////////////////////////////////////////////////////////////////////////////////
// XPS Driver SDK : smartcard_singlewire + omnikey chip example
////////////////////////////////////////////////////////////////////////////////
using System;
using System.IO;
using System.Text;
using dxp01sdk;
using SmartcardCommonNamespace;

public class CommandLineOptions {
    public string printerName;
    public string cardType;
    public bool print;
    public bool jobCompletion;
    public bool parkBack;
};

internal class smartcard_singlewire_omnikey {

    public static byte[] ReadCardIdCommandBytes = { 0xFF, 0xCA, 0x00, 0x00, 0x00 };
    private static byte[] MifareLoadKeyCommandBytes = { 0xFF, 0x82, 0x00, 0x01, 0x06, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
    private static byte[] MifareAuthenticateCommandBytes = { 0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, 0x01, 0x60, 0x01 };
    private static byte[] MifareReadBlockCommandBytes = { 0xFF, 0xB0, 0x00, 0x02, 0x10 };      // read 16 bytes
    private static byte[] MifareWriteBlockCommandBytes = { 0xFF, 0xD6, 0x00, 0x02, 0x10 };     // write 16 bytes

    private static byte[] IClassReadCardSNCommandBytes = { 0xFF, 0xB0, 0x00, 0x00, 0x08 };     // read SN using block command
    private static byte[] IclassLoadKeyCommandBytes = { 0xFF, 0x82, 0x20, 0x28, 0x08, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77 };
    private static byte[] IclassAuthenticateCommandBytes = { 0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, 0x03, 0x00, 0x28 };
    private static byte[] IclassReadBlockCommandBytes = { 0xFF, 0xB0, 0x00, 0x12, 0x08 };     // read 8 bytes
    private static byte[] IclassWriteBlockCommandBytes = { 0xFF, 0xD6, 0x00, 0x12, 0x08 };     // write 8 bytes

    private static byte[] HidProxReadPacsCommandBytes = { 0xFF, 0x70, 0x07, 0x6B, 0x07, 0xA0, 0x05, 0xA1, 0x03, 0x80, 0x01, 0x04, 0x00 };


    private static void Usage() {
        var thisExeName = Path.GetFileName(Environment.GetCommandLineArgs()[0]);
        Console.Write(thisExeName);
        Console.WriteLine(" demonstrates interactive mode parking in the");
        Console.WriteLine("smart card reader, performing single-wire smart card functions, and moving the");
        Console.WriteLine("card from the reader. It has options to print and poll for job completion.");
        Console.WriteLine("The omnikey reader supports multiple cards types, allowing you to select");
        Console.WriteLine("the type of card used. For each card type, the omnikey reader performs a");
        Console.WriteLine("series of functions, described below.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n <printername> -t <cardType> [-p [-c]");
        Console.WriteLine();
        Console.WriteLine("options:");
        Console.WriteLine("  -n <printername>. Required. Try -n \"XPS Card Printer\".");
        Console.WriteLine("  -t <cardtype>. Required. Specify one of the following:");
        Console.WriteLine("     -t Mifare. Reads the card ID and status, and reads and writes data to");
        Console.WriteLine("        block 2.");
        Console.WriteLine("     -t iClass. Reads the card ID and status, and reads and writes data to");
        Console.WriteLine("        block 18.");
        Console.WriteLine("     -t HIDProx. Reads the card ID (UID) and facility code (FAC).");
        Console.WriteLine("  -p Print simple black text on one or both sides of the card depending on the");
        Console.WriteLine("     printer used.");
        Console.WriteLine("  -c Poll for job completion.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n \"XPS Card Printer\" -t mifare");
        Console.WriteLine("  Parks a card in the smart card reader, connects to the Mifare chip,");
        Console.WriteLine("  performs the Mifare card functions, and moves the card from the reader.");
        Console.WriteLine();
        Console.WriteLine(thisExeName + " -n \"XPS Card Printer\" -t iClass -p -c");
        Console.WriteLine("  Parks a card in the smart card reader, connects to the iClass chip,"); 
        Console.WriteLine("  performs the iClass card functions, moves the card from the reader,");
        Console.WriteLine("  prints text and graphics on the front of the card, and polls for job");
        Console.WriteLine("  completion and displays the status.");
        Console.WriteLine();
        Environment.Exit(-1);
    }

    private static void InterpretResponseCode(byte[] bytes) {
        int nSize = bytes.Length;
        if (nSize < 2) {
            Console.WriteLine("expecting a two (or more) byte response code");
        }
        else {
            if (bytes[nSize - 2] == 0x90 && bytes[nSize - 1] == 0x00) {
                Console.WriteLine("response code = SUCCESS");
            }
            else if (bytes[nSize - 2] == 0x62 && bytes[nSize - 1] == 0x82) {
                Console.WriteLine("response code = End of file reached");
            }
            else if (bytes[nSize - 2] == 0x67 && bytes[nSize - 1] == 0x00) {
                Console.WriteLine("response code = Wrong Length");
            }
            else if (bytes[nSize - 2] == 0x65 && bytes[nSize - 1] == 0x81) {
                Console.WriteLine("response code = Memory Failure");
            }
            else if (bytes[nSize - 2] == 0x6A && bytes[nSize - 1] == 0x81) {
                Console.WriteLine("response code = Function not supported");
            }
            else if (bytes[nSize - 2] == 0x6B && bytes[nSize - 1] == 0x00) {
                Console.WriteLine("response code = Wrong parameter");
            }
            else if (bytes[nSize - 2] == 0x6F && bytes[nSize - 1] == 0x00) {
                Console.WriteLine("response code = Operation Failed");
            }
            else {
                Console.WriteLine("response code = Unknown Response Code");
            }
        }
    }


    private static CommandLineOptions GetCommandlineOptions(string[] args) {
        if (args.Length == 0) Usage();

        var commandLineOptions = new CommandLineOptions();
        var arguments = new CommandLine.Utility.Arguments(args);

        if (string.IsNullOrEmpty(arguments["n"])) {
            Usage();
            Environment.Exit(-1);
        }
        else if (string.IsNullOrEmpty(arguments["t"])) {
            Usage();
            Environment.Exit(-1);
        }
        else {
            // we might have a -n with no printer name:
            bool boolVal = false;
            if (Boolean.TryParse(arguments["n"], out boolVal)) {
                Console.WriteLine("printer name is required");
                Environment.Exit(-1);
            }
            commandLineOptions.printerName = arguments["n"];

            // we might have a -t with no card type:
            boolVal = false;
            if (Boolean.TryParse(arguments["t"], out boolVal)) {
                Console.WriteLine("card type is required");
                Environment.Exit(-1);
            }
            commandLineOptions.cardType = arguments["t"];

        }

        commandLineOptions.print = !string.IsNullOrEmpty(arguments["p"]);
        commandLineOptions.jobCompletion = !string.IsNullOrEmpty(arguments["c"]);

        return commandLineOptions;
    }









    ////////////////////////////////////////////////////////////////////////////////
    // Read Card ID()
    ////////////////////////////////////////////////////////////////////////////////
    private static void ReadCardID(SCard scard) {

        var exceptionText = new StringBuilder();

        SmartcardCommon.DisplayByteVectorAsHex("ReadCardId() - bytes sent", ReadCardIdCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(ReadCardIdCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error)scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read CardID command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("read CardID response", receivedBytes);

        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }

    }

    ////////////////////////////////////////////////////////////////////////////////
    // IClassReadCardSN()
    ////////////////////////////////////////////////////////////////////////////////
    private static void IClassReadCardSN(SCard scard) {

        Console.WriteLine();

        var exceptionText = new StringBuilder();

        SmartcardCommon.DisplayByteVectorAsHex("IClassReadCardSN() - bytes sent", IClassReadCardSNCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(IClassReadCardSNCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from ReadCardSN command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("Read Card Serial Number response", receivedBytes);

        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }

    }


    ///////////////////////////////////////////////////////////////////
    // MifareLoadKey() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void MifareLoadKey(SCard scard) {
        var exceptionText = new StringBuilder();

        SmartcardCommon.DisplayByteVectorAsHex("load a 6-byte Mifare key - bytes sent", MifareLoadKeyCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(MifareLoadKeyCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("load a 6-byte Mifare key response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }

    ///////////////////////////////////////////////////////////////////
    // MifareAuthenticate() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void MifareAuthenticate(SCard scard) {
        var exceptionText = new StringBuilder();

        Console.WriteLine();
        SmartcardCommon.DisplayByteVectorAsHex(" Authenticate block 1 - bytes sent", MifareAuthenticateCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(MifareAuthenticateCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex(" Authenticate block 1 response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }

    ///////////////////////////////////////////////////////////////////
    // MifareReadBlock() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void MifareReadBlock(SCard scard) {
        var exceptionText = new StringBuilder();

        // Read block 2 (16 bytes)
        Console.WriteLine();
        SmartcardCommon.DisplayByteVectorAsHex("Read block 2 - bytes sent", MifareReadBlockCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(MifareReadBlockCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("read block2 response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }


    ///////////////////////////////////////////////////////////////////
    // MifareWriteBlock() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void MifareWriteBlock(SCard scard) {
        var exceptionText = new StringBuilder();

        var testDataString = System.DateTime.Now.ToString("yyyy.mm.dd.hh.mm");
        var testDataBytes = Encoding.ASCII.GetBytes(testDataString);

        // write block 2 (16 bytes)
        // append blockData onto the end of the commandBytes:
        var commandBytes = new byte[MifareWriteBlockCommandBytes.Length + testDataBytes.Length ];
        System.Buffer.BlockCopy(MifareWriteBlockCommandBytes, 0, commandBytes, 0, MifareWriteBlockCommandBytes.Length);
        System.Buffer.BlockCopy(testDataBytes, 0, commandBytes, MifareWriteBlockCommandBytes.Length, testDataBytes.Length);

        Console.WriteLine();
        Console.WriteLine("writing '{0}' to chip", testDataString );
        SmartcardCommon.DisplayByteVectorAsHex("write block 2 - bytes sent", commandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(commandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from write block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("write block response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }

    /////////////////////////////////////////////////
    // using sample from document
    ///////////////////////////////////////////////
    private static void MifareReadWriteData(SCard scard) {
        Console.WriteLine();

        MifareLoadKey(scard);
        MifareAuthenticate(scard);
        MifareReadBlock(scard);
        MifareWriteBlock(scard);
        MifareReadBlock(scard);
    }




    ///////////////////////////////////////////////////////////////////
    // IclassLoadKey() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void IclassLoadKey(SCard scard) {
        var exceptionText = new StringBuilder();

        SmartcardCommon.DisplayByteVectorAsHex("load iClass key - bytes sent", IclassLoadKeyCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(MifareReadBlockCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response IclassLoadKey");
        }

        SmartcardCommon.DisplayByteVectorAsHex("load a iClass load key response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }

    ///////////////////////////////////////////////////////////////////
    // IclassAuthenticate() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void IclassAuthenticate(SCard scard) {
        var exceptionText = new StringBuilder();

        Console.WriteLine();
        SmartcardCommon.DisplayByteVectorAsHex(" Authenticate block 1 - bytes sent", IclassAuthenticateCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(IclassAuthenticateCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex(" Authenticate block 1 response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }

    ///////////////////////////////////////////////////////////////////
    // IclassReadBlock() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    //////////////////////////////////////////////////////////////////
    private static void IclassReadBlock(SCard scard) {
        var exceptionText = new StringBuilder();

        Console.WriteLine();
        SmartcardCommon.DisplayByteVectorAsHex("Read block 0x12 - bytes sent", IclassReadBlockCommandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(IclassReadBlockCommandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from read block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("read block 0x12 response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }



    private static void IclassWriteBlock(SCard scard) {
        var exceptionText = new StringBuilder();

        var testDataString = System.DateTime.Now.ToString("hh.mm.ss");
        var testDataBytes = Encoding.ASCII.GetBytes(testDataString);

        // write block 0x12 (8 bytes)
        // append blockData onto the end of the commandBytes:
        var commandBytes = new byte[IclassWriteBlockCommandBytes.Length + testDataBytes.Length];
        System.Buffer.BlockCopy(IclassWriteBlockCommandBytes, 0, commandBytes, 0, IclassWriteBlockCommandBytes.Length);
        System.Buffer.BlockCopy(testDataBytes, 0, commandBytes, IclassWriteBlockCommandBytes.Length, testDataBytes.Length);

        Console.WriteLine();
        Console.WriteLine("writing '{0}' to chip", testDataString);
        SmartcardCommon.DisplayByteVectorAsHex("write block 0x12 - bytes sent", commandBytes);

        var receivedBytes = new byte[0];

        long scardResult = scard.SCardTransmit(commandBytes, ref receivedBytes);
        Console.WriteLine("{0}", SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
            throw new Exception(exceptionText.ToString());
        }

        if (0 == receivedBytes.Length) {
            throw new Exception("no response from write block command");
        }

        SmartcardCommon.DisplayByteVectorAsHex("write block response", receivedBytes);
        if (receivedBytes.Length >= 2) {
            InterpretResponseCode(receivedBytes);
        }
    }



    /////////////////////////////////////////////////
    // IclassReadWriteData() 
    // - using sample from "Omnikey 5x27CK Software Developer Guide
    ///////////////////////////////////////////////
    private static void IclassReadWriteData(SCard scard) {

        Console.WriteLine();

        IclassLoadKey(scard);
        IclassAuthenticate(scard);
        IclassReadBlock(scard);
        IclassWriteBlock(scard);
        IclassReadBlock(scard);

    }


    ////////////////////////////////////////////////////////////////
    // HidProxReadPacs
    ///////////////////////////////////////////////////////////////
    private static byte[] HidProxReadPacs(SCard scard)
{

    Console.WriteLine();

    var exceptionText = new StringBuilder();
    var receivedBytes = new byte[0];

    // Read PACS info
    SmartcardCommon.DisplayByteVectorAsHex("read PACS info - bytes sent", HidProxReadPacsCommandBytes);

    long scardResult = scard.SCardTransmit(HidProxReadPacsCommandBytes, ref receivedBytes);
    if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
        exceptionText.Append(SmartcardCommon.SCardResultMessage(scardResult, "SCardTransmit()"));
        throw new Exception(exceptionText.ToString());
    }

    if (0 == receivedBytes.Length) {
        throw new Exception("no response from read PACS info");
    }

    SmartcardCommon.DisplayByteVectorAsHex("read PACS info response", receivedBytes);
    if (receivedBytes.Length >= 2) {
        InterpretResponseCode(receivedBytes);
    }

    return receivedBytes;
}


    ////////////////////////////////////////////////////////////////
    // HidProxParseH10301
    // 
    // H10301 PACS data is 26 bits (in 4 bytes of data)
    //    - 1 bit Parity, 8 bit Facility code, 16 bit CardID, 1 bit Parity, 7 "0's" for padding
    ///////////////////////////////////////////////////////////////

    private static void HidProxParseH10301(uint nPacs) 
{

    Console.WriteLine();

    // H10301 formatting = Parity bit + 8 bit Facility code + 16 bit CardID + parity bit + 7 "0's"
    ulong cardID = nPacs & (ulong)0x07fff80;
        Console.WriteLine("cardID = 0x{0:X}", cardID);
        cardID = cardID >> 7;
        Console.WriteLine("cardID = 0x{0:X}", cardID);
        ulong facilityCode = nPacs & (ulong)0x7f800000;
        Console.WriteLine("facilityCode = 0x{0:X}", facilityCode);

        facilityCode = facilityCode >> 23;
        Console.WriteLine("facilityCode = 0x{0:X}", facilityCode);

        Console.WriteLine("H10301 facility code = {0}, cardID = {1}", facilityCode, cardID);

}


    ////////////////////////////////////////////////////////////////
    // HidProxParseH10302AndH10304
    // 
    // H10302 PACS data is 37 bits (in 5 bytes of data)
    //    - 1 bit Parity, NO Facility code, 35 bit CardID, 1 bit Parity, 3 "0's" for padding
    // H10304 PACS data is 37 bits (in 5 bytes of data)
    //    - 1 bit Parity, 16 bit Facility code, 19 bit CardID, 1 bit Parity, 3 "0's" for padding
    // FYI - if there are 12 PACS bytes, this code does not know if H10302 or H10304 format was used.
    //
    ///////////////////////////////////////////////////////////////
    private static void HidProxParseH10302AndH10304(ulong nPacs) {

    Console.WriteLine();

    // H10302 formatting = Parity bit + NO Facility code + 35 bit CardID + parity bit + 3 "0's"
    ulong cardID = nPacs & (ulong)0x07ffffffff0;
    cardID = cardID >> 4;
    Console.WriteLine("H10302 facility code = No Facility Code, cardID = {0}", cardID);

    // H10304 formatting = "Parity bit + 16 bit Facility code + 19 bit CardID + parity bit + 3 '0's"
    cardID = nPacs & (ulong)0x07ffff0;
    cardID = cardID >> 4;
    ulong facilityCode = nPacs & (ulong)0x07fff800000;
    facilityCode = facilityCode >> 23;
    Console.WriteLine("H10304 facility code = {0}, cardID = {1}", facilityCode, cardID);
}


////////////////////////////////////////////////////////////////
// HidProxParsePacs
// 
// PACS data contains:
//  - first 5 bytes are "standard" PACS header (i.e. 9D 07 03 05 06) 
//  - 4 or 5 bytes of PACS data
//  - last 2 bytes (status code) need to be 0x90 0x00 (i.e. success)
// FYI - need at 11 or 12 bytes, or an exception is thrown
///////////////////////////////////////////////////////////////
private static void HidProxParsePacs(byte[] inputBytes) 
{
    Console.WriteLine();

    var exceptionText = new StringBuilder();
    var receivedBytes = new byte[0];
    int vectorLength = inputBytes.Length;
    byte[] successBytes = { 0x90, 0x00 };

    // convert vector of bytes into an unsigned long int
    if (vectorLength != 11 && vectorLength != 12) {
        exceptionText.Append("HidProxParsePacs() Invalid number of PACS bytes");
        throw new Exception(exceptionText.ToString());
    }
    if (inputBytes[vectorLength - 2] != successBytes[0] && inputBytes[vectorLength - 1] != successBytes[1]) {
        exceptionText.Append("HidProxParsePacs() invalid PACS information");
        throw new Exception(exceptionText.ToString());
    }

    uint nLongPacs = 0;
    if (vectorLength == 11) {
        // assuming H10301 format
        // need bytes 5 - 8.
        Console.WriteLine();

        for (int i = 5; i < 9; i++) {
            // shift nPacs to the left 8 bits
            nLongPacs = nLongPacs << 8;

            nLongPacs |= (uint) inputBytes[i];
            Console.Write("0x{0:X} ", nLongPacs);
        }

        Console.WriteLine();
        Console.WriteLine("nLongPacs(bytes) = 0x{0:X}", nLongPacs);
        Console.WriteLine("nLongPacs(unsignedLong) = {0}", nLongPacs);

        HidProxParseH10301(nLongPacs);

    }
    else {
        // Test code - change bytes 5 - 9 to the following
        // 1011 0011 1000 1111 0000 1111 1000 0011 1111 0000
        // This will yield the following results
        // H10302 - No facility code, cardid = 35 bits = 13840218175 (decimal)
        // H10304 - Facility code = 16 bits, CardID = 19 bits
        //  011 0011 1000 1111 0 - Facility code = 26398 (decimal)
        //                      000 1111 1000 0011 1111 - CardID = 63551 (decimal)
        //inputBytes[5] = 0xB3;        // 1011 0011 = 0xB3
        //inputBytes[6] = 0x8F;        // 1000 1111 = 0x8F
        //inputBytes[7] = 0x0F;        // 0000 1111 = 0x0F
        //inputBytes[8] = 0x83;        // 1000 0011 = 0x83
        //inputBytes[9] = 0xF0;        // 1111 0000 = 0xF0

        // assuming H10302 or H10304 formatting
        // need bytes 5 - 9.
        ulong nLongLongPacs = 0;
        for (int i = 5; i< 10; i++) {
            // shift nPacs to the left 8 bits
            nLongLongPacs = nLongLongPacs << 8;

            nLongLongPacs |= (ulong)inputBytes[i];
        }

        // print nLongLongPacs value in hex
        Console.WriteLine("PACS (bytes) =  = 0x{0:X}", nLongLongPacs);
        Console.WriteLine("PACS (unsigned integer)) = {0}", nLongLongPacs);

        HidProxParseH10302AndH10304(nLongLongPacs);
    }
}

    ////////////////////////////////////////////////////////////////////////////////
    // SmartCardControl()
    ////////////////////////////////////////////////////////////////////////////////
    private static void SmartCardControl(BidiSplWrap bidiSpl)
    {

        var exceptionText = new StringBuilder();
        var scard = new SCard(bidiSpl);

        Console.WriteLine();

        /*
            create a byte std::vector for the upcoming SCardControl() method.
            particular bytes should function with this type of contactless chip:
                 HID Prox
                 HID iClass
                 Mifare 1K Classic

           The control codes below are used to turn on / off  the field of the omnikey reader.
           The final byte is 0x00 for protocol contact(rf off) and 0x01 for contactless(rf on).
           It has been tested using contact connection with
             FW rev R4.1.2-3

           byte controlCode[] = { 0xff, 0x9a, 0x04, 0x00 };  // contact

           Make contact connection using SCARD_PROTOCOL_T0
       */

        // this control code has been tested on contactless using SCARD_PROTOCOL_CL
        // FW rev R4.1.2-3
        byte[] controlCode = { 0xff, 0x9a, 0x04, 0x01 };  // contactless

        SmartcardCommon.DisplayByteVectorAsHex("SCardControl bytes sent", controlCode);

        var receivedBytes = new byte[0];
        long scardResult = scard.SCardControl(controlCode, ref receivedBytes);
        Console.WriteLine(SmartcardCommon.SCardResultMessage(scardResult, "SCardControl()"));
        if (scard_error.SCARD_S_SUCCESS != (scard_error)scardResult)
        {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "SCardControl()");
            throw new Exception("SCardControl() fail: " + msg);
        }

        if (0 == receivedBytes.Length) {
            Console.WriteLine("no response from SCardControl command");
        } 
        else {
            SmartcardCommon.DisplayByteVectorAsHex("SCardControl() response", receivedBytes);
        }
    }


    /// <summary>
    /// Demonstrate the single-wire smartcard calls. The wrapper class we consume
    /// is similar to the Win32 ::Scard...() functions.
    /// <returns>
    /// throw an Exception if any error.
    /// </returns>
    /// </summary>
    private static void PersonalizeSmartcard(BidiSplWrap bidiSpl, string cardType ) {
        var scard = new SCard(bidiSpl);
        var protocol = (uint) scard_protocol.SCARD_PROTOCOL_DEFAULT;

        var scardResult = scard.SCardConnect(SCard.ChipConnection.contactless, ref protocol);

        Console.WriteLine("SCardConnect() result: {0}", scardResult);

        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "");
            throw new Exception("SCardConnect() fail: " + msg);
        }

        SmartcardCommon.DisplayProtocols(protocol);
        Console.WriteLine();

        SmartCardControl(bidiSpl);

        SmartcardCommon.GetSCardStatus(scard);

        ReadCardID(scard);
        Console.WriteLine();

        SmartcardCommon.ReadVendorInfo(scard);

        string strMifare = "Mifare";
        string strIClass = "iClass";
        string strHidProx = "HidProx";

        if (string.Equals(cardType, strMifare, StringComparison.CurrentCultureIgnoreCase)) {
            MifareReadWriteData(scard);
        }
        else if (string.Equals(cardType, strIClass, StringComparison.CurrentCultureIgnoreCase)) {
            IClassReadCardSN(scard);
            IclassReadWriteData(scard);
        }
        else if (string.Equals(cardType, strHidProx, StringComparison.CurrentCultureIgnoreCase)) {
            byte[] pacsBytes = HidProxReadPacs(scard);
            HidProxParsePacs(pacsBytes);
        }
        else {
            Console.WriteLine();
            string msg = String.Format("'{0}' is not a valid card type.", cardType);
            throw new Exception(msg);
        }

        Console.WriteLine();
        scardResult = scard.SCardDisConnect((int) scard_disposition.SCARD_LEAVE_CARD);
        Console.WriteLine("SCardDisConnect() result: {0}", scardResult);

        if (scard_error.SCARD_S_SUCCESS != (scard_error) scardResult) {
            var msg = SmartcardCommon.SCardResultMessage(scardResult, "");
            throw new Exception("SCardDisConnect() fail: " + msg);
        }
    }



    ////////////////////////////////////////////////////////////////////////////
    // Main()
    ////////////////////////////////////////////////////////////////////////////
    private static void Main(string[] args) {
        var commandLineOptions = GetCommandlineOptions(args);

        BidiSplWrap bidiSpl = null;
        int printerJobID = 0;

        try {
            bidiSpl = new BidiSplWrap();
            bidiSpl.BindDevice(commandLineOptions.printerName);

            // optional:
            string driverVersionXml = bidiSpl.GetPrinterData(strings.SDK_VERSION);
            Console.WriteLine(Environment.NewLine + "driver version: " + Util.ParseDriverVersionXML(driverVersionXml) + Environment.NewLine);

            string printerOptionsXml = bidiSpl.GetPrinterData(strings.PRINTER_OPTIONS2);
            PrinterOptionsValues printerOptionsValues = Util.ParsePrinterOptionsXML(printerOptionsXml);
            if ("Ready" != printerOptionsValues._printerStatus && "Busy" != printerOptionsValues._printerStatus) {
                throw new Exception(commandLineOptions.printerName + " is not ready. status: " + printerOptionsValues._printerStatus);
            }

            if ("Single wire" != printerOptionsValues._optionSmartcard) {
                var msg = string.Format("{0} needs 'Single wire' for smartcard option. '{1}' was returned.",
                    commandLineOptions.printerName,
                    printerOptionsValues._optionSmartcard);
                throw new Exception(msg);
            }

            string hopperID = string.Empty;
            printerJobID = Util.StartJob(
                bidiSpl,
                hopperID);

            SmartcardCommon.ParkCard(bidiSpl, commandLineOptions.parkBack);

            PersonalizeSmartcard(bidiSpl, commandLineOptions.cardType);

            SmartcardCommon.ResumeCard(bidiSpl, printerJobID, 0);

            if (commandLineOptions.print) {
                Util.PrintTextAndGraphics(bidiSpl, commandLineOptions.printerName);
                Util.WaitForWindowsJobID(bidiSpl, commandLineOptions.printerName);
            }

            bidiSpl.SetPrinterData(strings.ENDJOB);

            if (commandLineOptions.jobCompletion) {
                Util.PollForJobCompletion(bidiSpl, printerJobID);
            }
        }
        catch (BidiException e) {
            Console.WriteLine(e.Message);
            Util.CancelJob(bidiSpl, e.PrinterJobID, e.ErrorCode);
        }
        catch (Exception e) {
            Console.WriteLine(e.Message);
            if (0 != printerJobID) {
                Util.CancelJob(bidiSpl, printerJobID, 0);
            }
        }
        finally {
            bidiSpl.UnbindDevice();
        }
    }
}