////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Datacard Corporation.  All Rights Reserved.
//
// XPS Printer Driver SDK: Single-wire smartcard personalization c++ sample
// for a omnikey chip.
////////////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include <bidispl.h>
#include <iostream>
#include <iomanip>
#include <sstream>
#include <atltime.h>
#include "DXP01SDK.H"
#include "DXP01SDK_SCARD.H"
#include "XGetopt.h"
#include "util.h"
#include "CommandlineOptions.h"
#include "SmartcardCommon.h"

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

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

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


void usage()
{
    const auto msgFormat = LR"(
%s demonstrates interactive mode parking in the
smart card reader, performing single-wire smart card functions, and moving the 
card from the reader. It has options to print and poll for job completion.
The omnikey reader supports multiple cards types, allowing you to select 
the type of card used. For each card type, the omnikey reader performs a 
series of functions, described below.

%s -n <printername> -t <cardType> [-p] [-c] 

options:
  -n <printername>. Required. Try -n "XPS Card Printer".
  -t <cardtype>. Required. Specify one of the following:
     -t Mifare. Reads the card ID and status, and reads and writes data to
        block 2.
     -t iClass. Reads the card ID and status, and reads and writes data to
        block 18.
     -t HIDProx. Reads the card ID (UID) and facility code (FAC).
  -p Print simple black text on one or both sides of the card depending on 
     the printer used.
  -c Poll for job completion.

%s -n "XPS Card Printer" -t mifare
  Parks a card in the smart card reader, connects to the Mifare chip, 
  performs the Mifare card functions, and moves the card from the reader.

%s -n "XPS Card Printer" -t iClass -p -c
  Parks a card in the smart card reader, connects to the iClass chip, 
  performs the iClass card functions, moves the card from the reader, 
  prints text and graphics on the front of the card, and polls for job 
  completion and displays the status.
)";

    const CStringW shortExeName = util::GetShortExeName();
    CStringW msg;
    msg.Format(msgFormat, static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName),
        static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName), static_cast<LPCWSTR> (shortExeName));
    std::wcout << msg.GetBuffer() << std::endl;
    std::exit(0);
}


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

////////////////////////////////////////////////////////////////////////////////
// Read Card ID()
////////////////////////////////////////////////////////////////////////////////
void ReadCardID( dxp01sdk::SCard&    scard)
{
    std::cout << std::endl;

    std::stringstream exceptionText;

    const std::vector <byte> readCardIDCommand(
        ReadCardIdCommandBytes,
        ReadCardIdCommandBytes + ARRAYSIZE(ReadCardIdCommandBytes));

    SmartcardCommon::DisplayByteVectorAsHex("ReadCardId() - bytes sent", readCardIDCommand);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(readCardIDCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read CardID command");
    }

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

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

////////////////////////////////////////////////////////////////////////////////
// IClassReadCardSN()
////////////////////////////////////////////////////////////////////////////////
void IClassReadCardSN(dxp01sdk::SCard& scard)
{
    std::cout << std::endl;

    std::stringstream exceptionText;

    const std::vector <byte> readCardSNCommand(
        IClassReadCardSNCommandBytes,
        IClassReadCardSNCommandBytes + ARRAYSIZE(IClassReadCardSNCommandBytes));

    SmartcardCommon::DisplayByteVectorAsHex("IClassReadCardSN() - bytes sent", readCardSNCommand);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(readCardSNCommand, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from ReadCardSN command");
    }

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

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

}


///////////////////////////////////////////////////////////////////
// MifareLoadKey() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void MifareLoadKey(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // load a 6-byte Mifare key (of all FF's to Key Number 1
    const std::vector <byte> commandBytes(
        MifareLoadKeyCommandBytes,
        MifareLoadKeyCommandBytes + ARRAYSIZE(MifareLoadKeyCommandBytes));

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

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

///////////////////////////////////////////////////////////////////
// MifareAuthenticate() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void MifareAuthenticate(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // Authenticate block 1 with key number 1
    const std::vector <byte> commandBytes(
        MifareAuthenticateCommandBytes,
        MifareAuthenticateCommandBytes + ARRAYSIZE(MifareAuthenticateCommandBytes));

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex(" Authenticate block 1 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

///////////////////////////////////////////////////////////////////
// MifareReadBlock() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void MifareReadBlock(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // Read block 2 (16 bytes)
    //const byte MifareReadBlockCommandBytes[] = {0xFF, 0xB0, 0x00, 0x02, 0x10};
    const std::vector <byte> commandBytes(
        MifareReadBlockCommandBytes,
        MifareReadBlockCommandBytes + ARRAYSIZE(MifareReadBlockCommandBytes));

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("Read block 2 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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


///////////////////////////////////////////////////////////////////
// MifareWriteBlock() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void MifareWriteBlock(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    const CTime now(CTime::GetCurrentTime());
    const CString testDataCString(now.Format(TEXT("%Y.%m.%d.%H.%M")));
    const std::string testDataString = CT2A(testDataCString);
    std::vector <byte> testDataBytes(testDataString.c_str(), testDataString.c_str() + testDataString.length());

    // write block 2 (16 bytes)
    std::vector <byte> commandBytes(
        MifareWriteBlockCommandBytes,
        MifareWriteBlockCommandBytes + ARRAYSIZE(MifareWriteBlockCommandBytes));

    // append blockData onto the end of the commandBytes:
    commandBytes.insert(commandBytes.end(), testDataBytes.begin(), testDataBytes.end());


    std::cout << std::endl;
    std::cout << "writing '" << testDataString << "' to chip." << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("write block 2 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

/////////////////////////////////////////////////
// using sample from document
///////////////////////////////////////////////
void MifareReadWriteData(dxp01sdk::SCard& scard)
{
    std::cout << std::endl;

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




///////////////////////////////////////////////////////////////////
// IclassLoadKey() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void IclassLoadKey(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // load key
    const std::vector <byte> commandBytes(
        IclassLoadKeyCommandBytes,
        IclassLoadKeyCommandBytes + ARRAYSIZE(IclassLoadKeyCommandBytes));

    SmartcardCommon::DisplayByteVectorAsHex("load iClass key - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

///////////////////////////////////////////////////////////////////
// IclassAuthenticate() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void IclassAuthenticate(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // Authenticate block 1 with key number 1
    const std::vector <byte> commandBytes(
        IclassAuthenticateCommandBytes,
        IclassAuthenticateCommandBytes + ARRAYSIZE(IclassAuthenticateCommandBytes));

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex(" Authenticate block 1 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

///////////////////////////////////////////////////////////////////
// IclassReadBlock() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void IclassReadBlock(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // Read block 0x12 (8 bytes)
    const std::vector <byte> commandBytes(
        IclassReadBlockCommandBytes,
        IclassReadBlockCommandBytes + ARRAYSIZE(IclassReadBlockCommandBytes));

    std::cout << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("Read block 0x12 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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

///////////////////////////////////////////////////////////////////
// IclassWritelock() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
//////////////////////////////////////////////////////////////////
void IclassWriteBlock(dxp01sdk::SCard& scard)
{
    std::stringstream exceptionText;

    // write block 0x12 (8 bytes)
    const CTime now(CTime::GetCurrentTime());
    const CString testDataCString(now.Format(TEXT("%H:%M:%S")));
    const std::string testDataString = CT2A(testDataCString);
    std::vector <byte> testDataBytes(testDataString.c_str(), testDataString.c_str() + testDataString.length());

    std::vector <byte> commandBytes(
        IclassWriteBlockCommandBytes,
        IclassWriteBlockCommandBytes + ARRAYSIZE(IclassWriteBlockCommandBytes));

    // append blockData onto the end of the commandBytes:
    commandBytes.insert(commandBytes.end(), testDataBytes.begin(), testDataBytes.end());

    std::cout << std::endl;
    std::cout << "writing '" << testDataString << "' to chip." << std::endl;
    SmartcardCommon::DisplayByteVectorAsHex("write block 0x12 - bytes sent", commandBytes);

    std::vector <byte> receivedBytes;

    long scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read block command");
    }

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



/////////////////////////////////////////////////
// IclassReadWriteData() 
// - using sample from "Omnikey 5x27CK Software Developer Guide
///////////////////////////////////////////////
void IclassReadWriteData(dxp01sdk::SCard& scard)
{

    std::cout << std::endl;

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

}


////////////////////////////////////////////////////////////////
// HidProxReadPacs
///////////////////////////////////////////////////////////////
const std::vector <byte> HidProxReadPacs(dxp01sdk::SCard& scard)
{
    std::cout << std::endl;

    long scardResult = SCARD_S_SUCCESS;
    std::stringstream exceptionText;
    std::vector <byte> receivedBytes;

    // Read PACS info
    const std::vector <byte> commandBytes(
        HidProxReadPacsCommandBytes,
        HidProxReadPacsCommandBytes + ARRAYSIZE(HidProxReadPacsCommandBytes));

    SmartcardCommon::DisplayByteVectorAsHex("read PACS info - bytes sent", commandBytes);

    scardResult = scard.SCardTransmit(commandBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardTransmit()");
        throw std::runtime_error(exceptionText.str());
    }

    if (0 == receivedBytes.size()) {
        throw std::runtime_error("no response from read PACS info");
    }

    SmartcardCommon::DisplayByteVectorAsHex("read PACS info response", receivedBytes);

    return receivedBytes;
}



////////////////////////////////////////////////////////////////
// HidProxParseH10302AndH10304
// 
// 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
///////////////////////////////////////////////////////////////

void HidProxParseH10301(const unsigned long int nPacs)
{

    std::cout << std::endl;

    // H10301 formatting = Parity bit + 8 bit Facility code + 16 bit CardID + parity bit + 7 "0's"
    //unsigned long long int cardID = (nPacs >> 1) & (unsigned long long int)0x000000ffff;       // 
    unsigned long long int cardID = nPacs & (unsigned long long int)0x07fff80;
    cardID = cardID >> 7;
    unsigned long long int facilityCode = nPacs & (unsigned long long int)0x7f800000;
    facilityCode = facilityCode >> 23;
    std::cout << "H10301 facility code = " << facilityCode << ", cardID = " << cardID << std::endl;

}

////////////////////////////////////////////////////////////////
// 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.
//
///////////////////////////////////////////////////////////////
void HidProxParseH10302AndH10304(const unsigned long long int nPacs)
{

    std::cout << std::endl;

    // H10302 formatting = Parity bit + NO Facility code + 35 bit CardID + parity bit + 3 "0's"
    unsigned long long int cardID = nPacs & (unsigned long long int)0x07ffffffff0;
    cardID = cardID >> 4;
    std::cout << "H10302 facility code = No Facility Code, cardID = " << cardID << std::endl;

    // H10304 formatting = "Parity bit + 16 bit Facility code + 19 bit CardID + parity bit + 3 '0's"
    cardID = nPacs & (unsigned long long int)0x07ffff0;
    cardID = cardID >> 4;    
    unsigned long long int facilityCode = nPacs & (unsigned long long int)0x07fff800000; 
    facilityCode = facilityCode >> 23;
    std::cout << "H10304 facility code = " << facilityCode << ", cardID = " << cardID << std::endl;
}


////////////////////////////////////////////////////////////////
// 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
///////////////////////////////////////////////////////////////
void HidProxParsePacs(const std::vector <byte> inputBytes)
{
    std::cout << std::endl;

    std::stringstream exceptionText;
    std::vector <byte> receivedBytes;
    int vectorLength = (int)(inputBytes.size());
    const byte successBytes[] = {0x90, 0x00};

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

    if (vectorLength == 11) {
        // assuming H10301 format
        // need bytes 5 - 8.
        unsigned long int nLongPacs = 0;
        for (int i = 5; i < 9; i++) {
            // shift nPacs to the left 8 bits
            nLongPacs = nLongPacs << 8;

            nLongPacs |= (unsigned long int)inputBytes[i];
        }

        // print nLongPacs value in hex
        std::stringstream ss;
        ss << std::hex << nLongPacs;
        std::string str(ss.str());
        std::cout << "PACS (bytes) = " << str << std::endl;

        std::cout << "PACS (unsigned integer) = " << nLongPacs << std::endl;
        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.
        unsigned long long int nLongLongPacs = 0;
        for (int i = 5; i < 10; i++) {
            // shift nPacs to the left 8 bits
            nLongLongPacs = nLongLongPacs << 8;

            nLongLongPacs |= (unsigned long int)inputBytes[i];
        }

        // print nLongLongPacs value in hex
        std::stringstream ss;
        ss << std::hex << nLongLongPacs;
        std::string str(ss.str());
        std::cout << "PACS (bytes) = " << str << std::endl;

        std::cout << "PACS (unsigned integer) = " << nLongLongPacs << std::endl;
        HidProxParseH10302AndH10304(nLongLongPacs);
    }
}



////////////////////////////////////////////////////////////////////////////////
// SmartCardControl()
////////////////////////////////////////////////////////////////////////////////
void SmartCardControl(CComPtr <IBidiSpl>& bidiSpl)
{
    std::stringstream exceptionText;
    dxp01sdk::SCard scard(bidiSpl);

    std::cout << std::endl;

/*
     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
    const std::vector <byte> controlCodeBytes(controlCode, controlCode + ARRAYSIZE(controlCode));

    SmartcardCommon::DisplayByteVectorAsHex("SCardControl bytes sent", controlCodeBytes);
    
    std::vector <byte> receivedBytes;
    
    long scardResult = scard.SCardControl(controlCodeBytes, receivedBytes);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardControl()") << std::endl;
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardControl()");
        throw std::runtime_error(exceptionText.str());
    }
    
    if (0 == receivedBytes.size()) {
        std::cout << "no response from SCardControl command" << std::endl;
    }
    else {
        SmartcardCommon::DisplayByteVectorAsHex("SCardControl() response", receivedBytes);
    }
}


////////////////////////////////////////////////////////////////////////////////
// PersonalizeSmartcard()
// smartcard personalization using Win32 / WinSCard.H styled wrapper class.
////////////////////////////////////////////////////////////////////////////////
void PersonalizeSmartcard(CComPtr <IBidiSpl>& bidiSpl, CString cardType)
{

    std::stringstream exceptionText;
    dxp01sdk::SCard scard(bidiSpl);

    // prepare for a contactless connection
    DWORD protocol = SCARD_PROTOCOL_UNDEFINED;

    long scardResult = scard.SCardConnect(dxp01sdk::SCard::contactless, protocol);
    std::cout << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect(contactless)") << std::endl;

    // To make contact connection use code below
    //protocol = SCARD_PROTOCOL_T0
    //long scardResult = scard.SCardConnect(dxp01sdk::SCard::contact, protocol);

    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardConnect()");
        throw std::runtime_error(exceptionText.str());
    }

    SmartcardCommon::DisplayProtocols(protocol);

    try {
        SmartCardControl(bidiSpl);

        SmartcardCommon::GetSCardStatus(scard);  // reads ATR

        ReadCardID(scard);
        SmartcardCommon::ReadVendorInfo(scard);

        CString cstrMifare("Mifare");
        CString cstrIClass("iClass");
        CString cstrHidProx("HidProx");

        if (cardType.CompareNoCase(cstrMifare) == 0) {
            MifareReadWriteData(scard);
        }
        else if (cardType.CompareNoCase(cstrIClass) == 0) {
            IClassReadCardSN(scard);
            IclassReadWriteData(scard);
        }
        else if (cardType.CompareNoCase(cstrHidProx) == 0) {
            const std::vector <byte> pacsBytes = HidProxReadPacs(scard);
            HidProxParsePacs(pacsBytes);
        }
        else {
            std::cout << std::endl;
            exceptionText << "'" << CT2A(cardType.GetString()) << "'" << "is not a valid card type." ;
            throw std::runtime_error(exceptionText.str());
        }

    }
    catch (std::runtime_error& e) {
        scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
        throw std::runtime_error(e);
    }

    scardResult = scard.SCardDisConnect(SCARD_LEAVE_CARD);
    if (SCARD_S_SUCCESS != scardResult) {
        exceptionText << SmartcardCommon::SCardResultMessage(scardResult, "SCardDisConnect()");
        throw std::runtime_error(exceptionText.str());
    }

    std::cout << std::endl;
}

CommandlineOptions GetCommandLineOptions(const int argc, WCHAR* argv[])
{
    CommandlineOptions commandlineOptions;
    int c(0);
    while ((c = getopt(argc, argv, L"n:t:pc")) != EOF) {
        switch (c) {
        case L'n': commandlineOptions.printerName = optarg; break;
        case L'p': commandlineOptions.print = true; break;
        case L'c': commandlineOptions.cardCompletion = true; break;
        case L't': commandlineOptions.cardType = optarg; break;
        default: usage();
        }
    }

    if (commandlineOptions.printerName.IsEmpty() || commandlineOptions.cardType.IsEmpty()) {
        usage();
    }

    return commandlineOptions;
}

////////////////////////////////////////////////////////////////////////////////
// main()
////////////////////////////////////////////////////////////////////////////////
int wmain(int argc, WCHAR* argv[])
{
    const CommandlineOptions cmdLineOpts = GetCommandLineOptions(argc, argv);

    CComPtr <IBidiSpl> bidiSpl;
    int printerJobID(0);

    try {
        HRESULT hr = ::CoInitialize(nullptr);
        if (FAILED(hr)) {
            std::cerr << "::CoInitialize:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        // create an IBidiSpl COM smartpointer instance, and bind it to the
        // printer name. We use it for all subsequent call using the IBidi interface.
        hr = bidiSpl.CoCreateInstance(CLSID_BidiSpl);
        if (FAILED(hr)) {
            std::cerr << "bidiSpl.CoCreateInstance:" << " 0x" << std::hex << hr << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        hr = bidiSpl->BindDevice(cmdLineOpts.printerName, BIDI_ACCESS_USER);
        if (FAILED(hr)) {
            std::cerr << "BindDevice():" << " 0x" << std::hex << hr << " " << CT2A(cmdLineOpts.printerName) << " " << CT2A(util::Win32ErrorString(hr));
            return -1;
        }

        // optionally display driver version
        CString driverVersionXml = util::GetDriverVersionXML(bidiSpl);
        CString driverVersionString = util::ParseDriverVersionXML(driverVersionXml);
        std::cout << std::endl << "driver version: " << CW2A(driverVersionString) << std::endl << std::endl;

        CString printerOptionsXML = util::GetPrinterOptionsXML(bidiSpl);
        util::PrinterOptionsValues printerOptionsValues = util::ParsePrinterOptionsXML(printerOptionsXML);
        if ("Ready" != printerOptionsValues._printerStatus && "Busy" != printerOptionsValues._printerStatus) {
            std::cerr << "printer " << CT2A(cmdLineOpts.printerName) << " is not ready.";
            return -1;
        }

        // this application uses the single-wire method for communicating with
        // a smartcard reader in the printer. If the printer is not configured with
        // that option, we're done.
        if ("Single wire" != printerOptionsValues._optionSmartcard) {
            std::cerr << "printer '" << CT2A(cmdLineOpts.printerName) << "' does not have single-wire smartcard option. Exiting." << std::endl;;
            return -1;
        }

        CString hopperID;
        printerJobID = util::StartJob(
            bidiSpl,
            hopperID);

        const auto printerStatusValues = SmartcardCommon::ParkCard(bidiSpl, cmdLineOpts.parkBack);

        PersonalizeSmartcard(bidiSpl, cmdLineOpts.cardType);

        SmartcardCommon::ResumeCard(printerJobID, bidiSpl);

        if (cmdLineOpts.print) {
            const bool doTwoSided =
                printerOptionsValues._optionDuplex == "Auto" ||
                printerOptionsValues._optionDuplex == "Manual";

            // Important: util::PrintText() waits until driver gets all the print data.
            util::PrintText(cmdLineOpts.printerName, doTwoSided);
        }

        util::EndJob(bidiSpl);

        if (cmdLineOpts.cardCompletion) {
            util::PollForJobCompletion(bidiSpl, printerJobID);
        }
    }
    catch (util::BidiException& e) {
        std::cerr << e.what() << std::endl;
        util::CancelJob(bidiSpl, e.printerJobID, e.errorCode);
    }
    catch (std::runtime_error& e) {
        std::cerr << e.what() << std::endl;
        if (0 != printerJobID) {
            util::CancelJob(bidiSpl, printerJobID, 0);
        }
    }

    if (bidiSpl) {
        bidiSpl->UnbindDevice();
        bidiSpl = nullptr;
    }

    ::CoUninitialize();

    std::cout << std::endl;

    return 0;
}