Imports System
Imports System.Diagnostics
Imports System.IO
Imports System.Text

Imports FirmaSAT

'
' *  $Id: TestFirmaSat.vb $
' *   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`
' 



' Ported from C# to VB.NET using icsharpcode.net's SharpDevelop.

Namespace TestFirmaSAT
	''' <summary>
	''' Test examples for FirmaSAT .NET interface
	''' </summary>
	Class TestFirmaSAT
		Private Const MIN_FSA_VERSION As Integer = 90200

		' Test files required to exist in the current working directory:
		Private Shared arrFileNames As String() = 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> _
		Public Shared Sub Main(args As String())
			' Make sure minimum required version of FirmaSAT is installed...
			Console.WriteLine("FirmaSAT core Version is " & General.Version())
			If General.Version() < MIN_FSA_VERSION Then
				Console.WriteLine("FATAL ERROR: Require FirmaSAT core version " & MIN_FSA_VERSION & " or later.")
				Return
			End If

			' CHECK FOR REQUIRED FILES IN CURRENT WORKING DIRECTORY
			Console.WriteLine("Current working directory is {0}", Directory.GetCurrentDirectory())
			Dim missingFile As String = "STOPPED: Required file is missing in current working directory"
			For Each fn As String In arrFileNames
				If FileIsNotPresent(fn, missingFile) Then
					Return
				End If
			Next

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


			Dim doSome As Boolean = False
			For iarg As Integer = 0 To args.Length - 1
				If args(iarg) = "some" Then
					doSome = True
				End If
			Next

			'*************
			' DO THE TESTS
			'*************
			If doSome Then
				' 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()
			End If

			' ******************************************
			' FINALLY, DISPLAY QUICK INFO ABOUT THE CORE DLL
			Console.WriteLine(vbLf & "DETAILS 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(vbLf & "ALL TESTS COMPLETED.")
		End Sub

		Private Shared Sub 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()
		End Sub

		'********************
		' THE TEST MODULES...
		'********************

		Private Shared Sub test_General()
			Dim n As Integer
			Dim ch As Char
			Dim s As String
			'****************
			' GENERAL TESTS *
			'****************
			Console.WriteLine(vbLf & "INTERROGATE 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)
		End Sub

		Private Shared Sub test_ErrorLookup()
			Dim s As String
			'*****************
			' ERROR LOOKUP TESTS *
			'*********************
			Console.WriteLine(vbLf & "ERROR CODES:")
			For i As Integer = 0 To 9999
				s = General.ErrorLookup(i)
				If Not s.Equals([String].Empty) Then
					Console.WriteLine("{0}={1}", i, s)
				End If
			Next
		End Sub

		Private Shared Sub test_pipestring()
			Dim s As String
			Dim fname As String

			Console.WriteLine(vbLf & "FORM THE PIPESTRING FROM AN XML FILE:")
			fname = "cfdv33a-base.xml"
			s = Sat.MakePipeStringFromXml(fname)
			Console.WriteLine("Sat.MakePipeStringFromXml('{0}')=" & vbLf & "{1}", fname, s)
			Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed")
		End Sub

		Private Shared Sub test_SignAndVerify()
			Dim n As Integer
			Dim fname As String
			Dim newname As String, keyfile As String, password As String, certfile As String

			Console.WriteLine(vbLf & "SIGN 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(vbLf & "VERIFY 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")

		End Sub

		Private Shared Sub test_Digest()
			Dim s As String
			Dim fname As String

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

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

		End Sub

		Private Shared Sub test_Validate()
			Dim n As Integer
			Dim s As String
			Dim fname As String

			Console.WriteLine(vbLf & "TRY 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(vbLf & "VALIDATE 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(vbLf & "Using 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")


		End Sub

		Private Shared Sub test_ExtractAttribute()
			Dim s As String
			Dim fname As String
			Dim attributeName As String, elementName As String

			Console.WriteLine(vbLf & "EXTRACT 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})=" & vbLf & "{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})=" & vbLf & "{1}", fname, s, attributeName, elementName)
			Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed")

			Console.WriteLine(vbLf & "EXTRACT 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(vbLf & "EXTRACT 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")

		End Sub

		Private Shared Sub test_X509Certificate()
			Dim s As String
			Dim s1 As String
			Dim fname As String

			Console.WriteLine(vbLf & "GET 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(vbLf & "GET CERTIFICATE AS A BASE64 STRING:")
			fname = "emisor.cer"
			s = Sat.GetCertAsString(fname)
			Console.WriteLine("Sat.GetCertAsString('{0}')=" & vbLf & "{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")

		End Sub

		Private Shared Sub test_MakeSignature()
			Dim s As String
			Dim fname As String
			Dim keyfile As String, password As String

			Console.WriteLine(vbLf & "MAKE 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}')=" & vbLf & "{1}", fname, s)
			Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed")

		End Sub

		Private Shared Sub test_Detallista()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim keyfile As String, password As String
			Dim newname As String, certfile As String
			Dim attributeName As String, elementName As String

			Console.WriteLine(vbLf & "SIGN 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(vbLf & "EXTRACT 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")


		End Sub

		Private Shared Sub test_CertValidity()
			Dim s As String
			Dim fname As String

			Console.WriteLine(vbLf & "GET 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}')=" & vbTab & "{1}", fname, s)
			Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed")
			s = Sat.GetCertStart(fname)
			Console.WriteLine("Sat.GetCertStart('{0}') =" & vbTab & "{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}')=" & vbTab & "{1}", fname, s)
			Debug.Assert(s.Length > 0, "Sat.GetCertExpiry failed")
			s = Sat.GetCertStart(fname)
			Console.WriteLine("Sat.GetCertStart('{0}') =" & vbTab & "{1}", fname, s)
			Debug.Assert(s.Length > 0, "Sat.GetCertStart failed")

		End Sub

		Private Shared Sub test_CheckKeyAndCert()
			Dim n As Integer
			Dim keyfile As String, password As String, certfile As String

			Console.WriteLine(vbLf & "CHECK 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")


		End Sub

		Private Shared Sub test_ReceiptVersion()
			Dim n As Integer
			Dim fname As String

			Console.WriteLine(vbLf & "FIND 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")


		End Sub

		Private Shared Sub test_Tfd()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim newname As String, keyfile As String, password As String, certfile As String
			Dim s1 As String

			Console.WriteLine(vbLf & "CREATE 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}') =" & vbLf & "{1}", fname, s)

			Console.WriteLine(vbLf & "FORM DIGEST OF PIPESTRING FOR TFD:")
			s = Tfd.MakeDigestFromXml(fname)
			Console.WriteLine("Tfd.MakeDigestFromXml('{0}')=" & vbLf & "{1}", fname, s)

			Console.WriteLine(vbLf & "EXTRACT DIGEST FROM TFD SELLOSAT:")
			' NB certFile is required for Tfd
			s1 = Tfd.ExtractDigestFromSignature(fname, certfile)
			Console.WriteLine("Tfd.ExtractDigestFromSignature('{0}')=" & vbLf & "{1}", fname, s1)

			' Check the two digests match
			Debug.Assert([String].Compare(s, s1, True) = 0, "Digests do not match")

			Console.WriteLine(vbLf & "PRETEND 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}')=" & vbLf & "{1}", fname, s)
			s1 = Sat.GetXmlAttribute(fname, "SelloSAT", "TimbreFiscalDigital")
			' NB Capital 'S' in Sello for TFD v1.1
			Console.WriteLine("Correct=" & vbLf & "{0}", s1)
			Debug.Assert([String].Compare(s, s1, True) = 0, "selloSAT values do not match")

			Console.WriteLine(vbLf & "ADD 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)" & vbLf, 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)" & vbLf, newname, n)
			Debug.Assert(0 = n, "Sat.ValidateXml failed")

			Console.WriteLine(vbLf & "VERIFY 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")


		End Sub

		Private Shared Sub test_BOM()
			Dim n As Integer
			Dim fname As String
			Dim newname As String

			Console.WriteLine(vbLf & "ADD 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))

		End Sub

		Private Shared Sub test_ConsecutiveElements()
			Dim s As String
			Dim fname As String
			Dim attributeName As String, elementName As String

			Console.WriteLine(vbLf & "EXTRACT ATTRIBUTES FROM CONSECUTIVE ELEMENTS:")
			fname = "ejemplo_v32-tfd2015.xml"
			attributeName = "descripcion"
			elementName = "cfdi:Concepto"
			Console.WriteLine("For file '{0}'", fname)
			Dim eName As String = Nothing
			For i As Integer = 1 To 10
				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 Then
					' [2020-07-06] Changed from empty string
					Exit For
				End If
				Console.WriteLine("{0}", s)
			Next
			Console.WriteLine()


		End Sub

		Private Shared Sub test_PrivateKey()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim keyfile As String, password As String, certfile As String
			Dim newname As String
			Dim newpassword As String

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

			Console.WriteLine(vbLf & "WRITE 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(vbLf & "SAVE 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)


		End Sub

		Private Shared Sub test_QueryCert()
			Dim s As String
			Dim fname As String

			Console.WriteLine(vbLf & "GET 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(vbLf & "TEST 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(vbLf & "QUERY 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(vbLf & "QUERY 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")


		End Sub

		Private Shared Sub test_SignInMemory()
			Dim n As Integer
			Dim fname As String
			Dim keyfile As String, password As String, certfile As String
			Dim xmlArr As Byte()
			Dim b As Byte()

			Console.WriteLine(vbLf & "SIGN 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)
			Dim s1 As String = ""
			Dim s2 As String = ""
			' Sign it and receive result back as a byte array...
			b = Sat.SignXmlToBytes(xmlArr, keyfile, password, certfile, 0)
			If b.Length = 0 Then
				Console.WriteLine("ERROR={0}", General.LastError())
			Else
				' Convert this to a string
				s1 = System.Text.Encoding.UTF8.GetString(b)
				Console.WriteLine("XML-OUT=" & vbLf & "---" & vbLf & "{0}" & vbLf & "---" & vbLf, s1)
				Console.WriteLine("From bytes: XML-OUT: {0} bytes --> {1} chars", b.Length, s1.Length)
			End If

			' Sign the file instead and receive result back as a byte array...
			b = Sat.SignXmlToBytes(fname, keyfile, password, certfile, 0)
			If b.Length = 0 Then
				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))
			End If


		End Sub

		Private Shared Sub test_UUID()
			Dim s As String

			Console.WriteLine(vbLf & "GENERATE 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)

		End Sub

		Private Shared Sub test_Retenciones()
			Dim n As Integer
			Dim s As String
			Dim fname As String

			Console.WriteLine(vbLf & "WORK 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")


		End Sub

		Private Shared Sub test_Contabilidad()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim newname As String, keyfile As String, password As String, certfile As String

			Console.WriteLine(vbLf & "WORK 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() ->" & vbLf & "{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(vbLf & "SUPPORT 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)

		End Sub

		Private Shared Sub test_ConVol()
			Dim n As Integer
			Dim fname As String
			Dim newname As String, keyfile As String, password As String, certfile As String

			Console.WriteLine(vbLf & "WORK 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")


		End Sub

		Private Shared Sub test_InsertCert()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim newname As String, password As String, certfile As String
			Dim cerStr As String
			Dim xmlStr As String
			Dim keyStr As String
			Dim newStr As String
			Dim b As Byte()

			Console.WriteLine(vbLf & "INSERT 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", Nothing)
			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(vbLf & "INSERT 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 Then
				DisplayError()
			End If
			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 Then
				DisplayError()
			End If

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

			' Verify that it worked
			n = Sat.VerifySignature(newStr)
			Console.WriteLine("Sat.VerifySignature() returns {0} (0 => OK)", n)
			If n <> 0 Then
				DisplayError(n)
			End If
			Debug.Assert(0 = n, "Sat.VerifySignature failed")

		End Sub

		Private Shared Sub test_GetXmlAttribute_Advanced()
			Dim s As String
			Dim fname As String
			Dim attributeName As String, elementName As String
			Dim xbase As String, xpath As String, val As String
			Dim i As Integer, j As Integer

			Console.WriteLine(vbLf & "XPATH 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...")
			i = 1
			While True
				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 Then
					Exit While
				End If
				i += 1
			End While
			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 Then
				Console.WriteLine("ERROR={0}", General.LastError())
			End If
			Debug.Assert(s.Length = 0, "Sat.GetXmlAttribute failed")

			Console.WriteLine(vbLf & "ADVANCED 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(vbLf & "USE 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"
			i = 1
			While True
				' FOREACH //Conceptos/Concepto[i] element output the value of Importe
				xpath = xbase & "[" & i & "]"
				val = Sat.GetXmlAttribute(fname, attributeName, xpath)
				If Sat.XmlNoMatch() = val Then
					Exit While
				End If
				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]
				j = 1
				While True
					Dim xpath1 As String = xpath & "//Traslado[" & j & "]"
					val = Sat.GetXmlAttribute(fname, attributeName, xpath1)
					If Sat.XmlNoMatch() = val Then
						Exit While
					End If
					Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val)
					j += 1
				End While

				' FOREACH //Conceptos/Concepto[i]//Retencion[j] element output the value of Importe
				j = 1
				While True
					Dim xpath1 As String = xpath & "//Retencion[" & j & "]"
					val = Sat.GetXmlAttribute(fname, attributeName, xpath1)
					If Sat.XmlNoMatch() = val Then
						Exit While
					End If
					Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val)
					j += 1
				End While
				i += 1
			End While
			' 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]
			j = 1
			While True
				Dim xpath1 As String = xpath & "//Retencion[" & j & "]"
				val = Sat.GetXmlAttribute(fname, attributeName, xpath1)
				If Sat.XmlNoMatch() = val Then
					Exit While
				End If
				Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val)
				j += 1
			End While

			' FOREACH /Comprobante/Impuestos//Traslado[j] element output the value of Importe
			j = 1
			While True
				Dim xpath1 As String = xpath & "//Traslado[" & j & "]"
				val = Sat.GetXmlAttribute(fname, attributeName, xpath1)
				If Sat.XmlNoMatch() = val Then
					Exit While
				End If
				Console.WriteLine("{0}/@{1}='{2}'", xpath1, attributeName, val)
				j += 1
			End While

		End Sub

		''' <summary>
		''' Tests passing xmlDoc parameter as a Unicode UTF-16 string. Improved in [v9.2].
		''' </summary>
		Private Shared Sub test_XMLasUnicodeString()
			Dim n As Integer
			Dim s As String
			Dim fname As String
			Dim newname As String, password As String
			Dim xmlStr As String
			Dim elemName As String, attrName As String
			Dim digest1 As String, digest2 As String


			Console.WriteLine(vbLf & "READ 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(vbLf & "GET 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 Then
				DisplayError()
			End If
			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 Then
				DisplayError()
			End If
			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 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.GetXmlAttribute failed")

			Console.WriteLine(vbLf & "CHECK XML IS OK USING UTF-16 STRING...")
			n = Sat.ValidateXml(xmlStr)
			Console.WriteLine("Sat.ValidateXml returns {0} (expecting 0)", n)
			If n <> 0 Then
				DisplayError(n)
			End If
			Debug.Assert(0 = n, "Sat.ValidateXml failed")

			Console.WriteLine(vbLf & "MAKE PIPE STRING USING UTF-16 STRING...")
			s = Sat.MakePipeStringFromXml(xmlStr)
			Console.WriteLine("PIPESTRING=" & vbLf & "{0}", s)
			If s.Length = 0 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.MakePipeStringFromXml failed")

			Console.WriteLine(vbLf & "FORM MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING...")
			s = Sat.MakeDigestFromXml(xmlStr)
			Console.WriteLine("DIGEST={0}", s)
			If s.Length = 0 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.MakeDigestFromXml failed")

			Console.WriteLine(vbLf & "INSERT 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 Then
				DisplayError(n)
			End If
			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(vbLf & "SIGN 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
			Dim keyStr As String = Sat.GetKeyAsString("emisor.key", password, KeyOption.EncryptedPEM)
			Console.WriteLine("Key string is {0} characters", keyStr.Length)
			If keyStr.Length = 0 Then
				DisplayError()
			End If
			' And the matching cert
			Dim cerStr As String = Sat.GetCertAsString("emisor.cer")
			Console.WriteLine("Cert string is {0} characters", cerStr.Length)
			If cerStr.Length = 0 Then
				DisplayError()
			End If

			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 Then
				DisplayError(n)
			End If
			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.
			Dim xmlArr As Byte() = 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
			Dim xmlArrSigned As Byte() = 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(vbLf & "CHECK 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 Then
				DisplayError(n)
			End If
			Debug.Assert(0 = n, "Sat.ValidateXml failed")

			Console.WriteLine(vbLf & "VALIDATE THE SIGNATURE USING UTF-16 STRING...")
			n = Sat.VerifySignature(xmlStr)
			Console.WriteLine("Sat.VerifySignature(xmlStr-signed) returns {0} (expecting 0)", n)
			If n <> 0 Then
				DisplayError(n)
			End If
			Debug.Assert(0 = n, "Sat.VerifySignature failed")

			Console.WriteLine(vbLf & "GET 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 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.QueryCert failed")

			Console.WriteLine(vbLf & "MAKE SIGNATURE USING UTF-16 STRING...")
			s = Sat.MakeSignatureFromXml(xmlStr, keyStr, password)
			Console.WriteLine("Sello={0}", s)
			If s.Length = 0 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.MakeSignatureFromXml failed")

			Console.WriteLine(vbLf & "EXTRACT SIGNATURE DIGEST USING UTF-16 STRING...")
			s = Sat.ExtractDigestFromSignature(xmlStr)
			Console.WriteLine("digest1={0}", s)
			If s.Length = 0 Then
				DisplayError()
			End If
			Debug.Assert(s.Length > 0, "Sat.ExtractDigestFromSignature failed")
			digest1 = s

			Console.WriteLine(vbLf & "MAKE MESSAGE DIGEST OF PIPE STRING USING UTF-16 STRING...")
			s = Sat.MakeDigestFromXml(xmlStr)
			Console.WriteLine("digest2={0}", s)
			If s.Length = 0 Then
				DisplayError()
			End If
			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")




		End Sub


		'*************************************
		' LOCAL UTILITIES...
		'*************************************

		Private Shared Function FileIsNotPresent(filePath As String, message As String) As Boolean
			Dim fi As New FileInfo(filePath)
			If Not fi.Exists Then
				Console.WriteLine(vbLf & "{0}: {1}", message, filePath)
				Return True
			End If
			Return False
		End Function
		Private Shared Function FileLength(filePath As String) As Long
			Dim fi As New FileInfo(filePath)
			If Not fi.Exists Then
				Return -1
			End If
			Return fi.Length
		End Function

		Private Shared Function FileHasBom(filePath As String) As Boolean
			Const  kNBYTES As Integer = 3
			Dim buf As Byte() = New Byte(kNBYTES - 1) {}
			Dim finfo As New FileInfo(filePath)
			Debug.Assert(finfo.Exists, "File '" & filePath & "' does not exist.")
			If finfo.Exists Then
				Dim fsi As FileStream = finfo.OpenRead()
				Dim br As New BinaryReader(fsi)
				buf = br.ReadBytes(kNBYTES)
				br.Close()
				fsi.Close()
			End If
			' BOM consists of three bytes (0xEF, 0xBB, 0xBF) 

			Return (buf(0) = &Hef AndAlso buf(1) = &Hbb AndAlso buf(2) = &Hbf)
		End Function

		'* Display error - overloaded 

		Private Shared Sub DisplayError()
			Console.WriteLine("ERROR: {0}", General.LastError())
		End Sub
		Private Shared Sub DisplayError(errCode As Integer)
			Dim s As String = General.LastError()
			Console.WriteLine("ERROR {0}: {1}: {2}", errCode, General.ErrorLookup(errCode), s)
		End Sub
	End Class
End Namespace