program TestSc14n; { Some tests for the Delphi/FreePascal interface to SC14N <https://www.cryptosys.net/sc14n/> $Id: TestSc14n.pas $ $Date: 2023-04-14 11:01 $ $Revision: 1.0.1 $ ************************** LICENSE ***************************************** Copyright (C) 2023 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> **************************************************************************** } {$APPTYPE CONSOLE} {$mode Delphi} {$ASSERTIONS ON} uses SysUtils, diSc14n; const { WARNING: this is where we expect to find the test files. CHANGE THIS TO SUIT } TEST_DIR = '.\Test'; CRLF = #13 + #10; { Declare forward functions for safer wrapper functions returning strings. } Function c14nFile2Digest(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward; Function c14nFile2String(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward; Function c14nString2String(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward; Function c14nString2Digest(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; forward; Function sc14nErrorLookup(nErrCode : LongInt): AnsiString; forward; Function sc14nLastError(): AnsiString; forward; // DO SOME TESTS Procedure do_tests; var n : Integer; s : AnsiString; buf : AnsiString; ch : Char; nchars : Integer; fname : AnsiString; outfile : AnsiString; begin WriteLn('Running ' + ExtractFileName(ParamStr(0)) + ' at ' + DateTimeToStr(Now)); // Set current working directory to find test files ChDir(TEST_DIR); // INTERROGATE THE CORE DLL // Either return an integer value or fill a pre-dimensioned output string buffer. // (You could write wrapper functions for the 'Gen' functions here that output to a string) WriteLn(CRLF + 'GENERAL:'); WriteLn('Interrogate the core DLL:'); WriteLn('Version='+(IntToStr(SC14N_Gen_Version))); nchars := SC14N_Gen_CompileTime(NIL, 0); WriteLn('CompileTime returns nchars=' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0,nchars)); nchars := SC14N_Gen_CompileTime(PAnsiChar(buf), nchars); WriteLn('CompileTime=' + buf); Assert (nchars > 0); ch := Chr(SC14N_Gen_LicenceType()); WriteLn('LicenceType='+ ch); nchars := SC14N_Gen_ModuleName(NIL, 0, 0); WriteLn('ModuleName returns nchars=' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); SC14N_Gen_ModuleName(PAnsiChar(buf), nchars, 0); WriteLn('ModuleName=' + Trim(string(buf))); nchars := SC14N_Gen_Platform(NIL, 0); WriteLn('Platform returns nchars=' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); SC14N_Gen_Platform(PAnsiChar(buf), nchars); WriteLn('Platform=' + Trim(string(buf))); // Compute C14N of FirstName element using tag fname := 'test_bruce_utf8.xml'; WriteLn('FILE: ' + fname); outfile := 'fileout.xml'; n := C14N_File2File(outfile, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('C14N_File2File returns '+(IntToStr(n))+' (expecting 0)'); Assert(0 = n); WriteLn('Created output file: ' + outfile); // Compute digest values the long way... // Compute SHA-1 digest of C14N'd data nchars := C14N_File2Digest(NIL, 0, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('C14N_File2Digest returns nchars=' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_File2Digest(PAnsiChar(buf), nchars, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('File2Digest(SHA-1)=' + Trim(string(buf))); Assert('Qy90ICPAAbnjWl/UpAn37sXS1wU=' = buf); // Same using SHA-256 nchars := C14N_File2Digest(NIL, 0, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256); WriteLn('C14N_File2Digest returns nchars=' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_File2Digest(PAnsiChar(buf), nchars, fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256); WriteLn('File2Digest(SHA-256)=' + Trim(string(buf))); Assert('93ljdxd4XQ2k66gOZai6a3bhjVO5t6XNqqBuArFyhc0=' = buf); // Repeat using wrapper function s := c14nFile2Digest(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256); WriteLn('c14nFile2Digest(SHA-256) returns [' + s + ']'); Assert('93ljdxd4XQ2k66gOZai6a3bhjVO5t6XNqqBuArFyhc0=' = s); s := c14nFile2Digest(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA512); WriteLn('c14nFile2Digest(SHA-512) returns ' + CRLF + '[' + s + ']'); // Extract C14N output from file into a string s := c14nFile2String(fname, 'FirstName', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('c14nFile2String returns:' + CRLF + s); WriteLn('--expecting:' + CRLF + '<FirstName xml:id="F01">BruceƱ</FirstName>'); // String --> String s := c14nString2String('<a><b xyz="last" abc="first" /></a>', 'b', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('c14nString2String returns [' + s + ']'); // String --> Digest s := c14nString2Digest('<a><b xyz="last" abc="first" /></a>', 'b', '', SC14N_TRAN_SUBSETBYTAG or SC14N_DIG_SHA256); WriteLn('c14nString2Digest returns [' + s + ']'); WriteLn('Display some error codes...'); for n := 0 to 5 do begin WriteLn('Error(' + IntToStr(n) + ')=' + sc14nErrorLookup(n)); end; WriteLn('Cause a deliberate error...'); n := C14N_File2File(outfile, 'missing.file', 'FirstName', '', SC14N_TRAN_SUBSETBYTAG); WriteLn('C14N_File2File(missing.file) returns n=', n); WriteLn('Error(' + IntToStr(n) + ')=' + sc14nErrorLookup(n)); WriteLn('LastError=' + sc14nLastError()); // An error in a wrapper function will raise an exception try s := c14nFile2Digest(fname, 'BadName', '', SC14N_TRAN_SUBSETBYTAG); except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; WriteLn('...END OF deliberate errors.'); WriteLn('SC14N Version=', SC14N_Gen_Version()); WriteLn(CRLF+'ALL DONE.'); end; { WRAPPER FUNCTIONS -- functions that return a string directly in a safe manner. } { Our crude Exception class } type ESc14nException = Class(Exception); { Compute digest value of C14N transformation (file-to-digest). @param(szInputFile Name of input file containing XML document to be processed.) @param(szNameOrId Tag name or Id to include or omit.) @param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.) @param(nOptions Option flags.) @returns(Digest value as base64-encoded string, or an empty string on error.) @raises(ESc14nException if the core function fails for any reason.) } Function c14nFile2Digest(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := C14N_File2Digest(NIL, 0, szInputFile, szNameOrId, szParams, nOptions); if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_File2Digest(PAnsiChar(buf), nchars, szInputFile, szNameOrId, szParams, nOptions); Result := buf end; { Perform C14N transformation of XML document (file-to-memory). @param(szInputFile Name of input file containing XML document to be processed.) @param(szNameOrId Tag name or Id to include or omit.) @param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.) @param(nOptions Option flags.) @returns(UTF-8-encoded string containing transformed data, or an empty string on error.) @raises(ESc14nException if the core function fails for any reason.) } Function c14nFile2String(szInputFile : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := C14N_File2String(NIL, 0, szInputFile, szNameOrId, szParams, nOptions); if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_File2String(PAnsiChar(buf), nchars, szInputFile, szNameOrId, szParams, nOptions); Result := buf end; { Perform C14N transformation of XML document (string-to-string). @param(szInput String containing UTF-8-encoded XML data.) @param(szNameOrId Tag name or Id to include or omit.) @param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.) @param(nOptions Option flags.) @returns(UTF-8-encoded string containing transformed data, or an empty string on error.) @raises(ESc14nException if the core function fails for any reason.) } Function c14nString2String(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := C14N_String2String(NIL, 0, szInput, Length(szInput), szNameOrId, szParams, nOptions); if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_String2String(PAnsiChar(buf), nchars, szInput, Length(szInput), szNameOrId, szParams, nOptions); Result := buf end; { Compute digest value of C14N transformation of XML document (string-to-digest). @param(szInput String containing UTF-8-encoded XML data.) @param(szNameOrId Tag name or Id to include or omit.) @param(szParams InclusiveNamespaces PrefixList parameter for exclusive c14n.) @param(nOptions Option flags.) @returns(Digest value as base64-encoded string, or an empty string on error.) @raises(ESc14nException if the core function fails for any reason.) } Function c14nString2Digest(szInput : AnsiString; szNameOrId : AnsiString; szParams : AnsiString; nOptions : LongInt): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := C14N_String2Digest(NIL, 0, szInput, Length(szInput), szNameOrId, szParams, nOptions); if nchars < 0 then raise ESc14nException.Create('SC14N error: ' + IntToStr(nchars)); buf := AnsiString(StringOfChar(#0, nchars)); C14N_String2Digest(PAnsiChar(buf), nchars, szInput, Length(szInput), szNameOrId, szParams, nOptions); Result := buf end; { Look up description for error code. @param(nErrCode Value of error code to lookup (may be positive or negative).) @returns(Error message, or empty string if no corresponding error code.) } Function sc14nErrorLookup(nErrCode : LongInt): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := SC14N_Err_ErrorLookup(NIL, 0, nErrCode); if nchars < 0 then Exit(''); buf := AnsiString(StringOfChar(#0, nchars)); SC14N_Err_ErrorLookup(PAnsiChar(buf), nchars, nErrCode); Result := buf end; { Retrieve the last error message (if available). @returns(String with more information about the last error.) @note(Not all functions set this.) } Function sc14nLastError(): AnsiString; var buf : AnsiString; nchars : Integer; begin nchars := SC14N_Err_LastError(NIL, 0); if nchars < 0 then Exit(''); buf := AnsiString(StringOfChar(#0, nchars)); SC14N_Err_LastError(PAnsiChar(buf), nchars); Result := buf end; { MAIN TEST PROCEDURE } begin try do_tests; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end.