Tuesday, April 17, 2012

console app that monitors smart cards

Here is a console app that demonstrates how to tie the smart card functions already covered into a working monitor that prints reader state changes and the certs and subject names for the cards that are present.

#pragma once

// Exclude rarely-used stuff from
// Windows headers
#define WIN32_LEAN_AND_MEAN
// skip min/max macros from
// Windows Headers
#define NOMINMAX
// Windows Header Files:
#include <windows.h>
#include <Unknwn.h>
#include <winscard.h>
#include <ncrypt.h>
#include <Wincrypt.h>
#include <credentialprovider.h>

// C++ library header files:
#include <type_traits>
#include <algorithm>
#include <new>
#include <memory>
#include <utility>
#include <limits>
#include <iterator>
#include <thread>
#include <future>
#include <mutex>
#include <vector>
#include <iostream>
#include <iomanip>

// local header files:
#define LIBRARIES_NAMESPACE nslib
#include <libraries.h>
namespace lib = LIBRARIES_NAMESPACE;

#include "..\win32project1\scard_monitor.h"

Set a breakpoint in each of these functions and see where error codes first make an appearance in this module. the static anchor is a device to make sure that the compiler does not eliminate or fold them together.

void unique_error_report_initiated(
  HRESULT value, 
  unique_hresult_def::tag &&)
{
  static HRESULT anchor;
  anchor = value;
}

void unique_error_report_reset(
  HRESULT value, 
  unique_hresult_def::tag &&)
{
  static HRESULT anchor;
  anchor = value;
}

void unique_error_report_initiated(
  DWORD value, 
  unique_winerror_def::tag &&)
{
  static DWORD anchor;
  anchor = value;
}

void unique_error_report_reset(
  DWORD value, 
  unique_winerror_def::tag &&)
{
  static DWORD anchor;
  anchor = value;
}

Helper functions to print status and certificate properties.

template<typename Certificate>
void printCertificates(
  lib::rng::range<Certificate> certificates)
{
  for (auto & key : certificates) {
    unique_winerror winerror;

    PCCERT_CONTEXT certcontext = (
      CertCreateCertificateContext(
        X509_ASN_ENCODING | 
          PKCS_7_ASN_ENCODING,
        &key.cert[0],
        key.cert.size()
      )
    );
    winerror = make_winerror_if(!certcontext);

    if (!winerror) {
      std::wcout 
        << L"could not get cert context" 
        << std::endl;
      continue;
    }

    DWORD sizesubject = 0;
    std::wstring subjectname;
    for (bool getsize = true; ; getsize = false) {
      sizesubject = CertGetNameString(
        certcontext,
        CERT_NAME_FRIENDLY_DISPLAY_TYPE,
        0,
        NULL,
        getsize ? nullptr : &subjectname[0],
        sizesubject
      );
      if (sizesubject == 1) {
        std::wcout 
          << L"could not get subject name" 
          << std::endl;
        break;
      }
      if (getsize) {
        subjectname.resize(sizesubject - 1);
      } else {
        std::wcout 
          << L"key name: " << key.key.c_str() 
          << L" subject name: " << subjectname.c_str() 
          << std::endl;
        break;
      }
    }
  }
}

template<typename Stream>
Stream& printSCardState(Stream& stream, DWORD state)
{
  stream
    << ((state == SCARD_STATE_UNAWARE)  ? 
      L" SCARD_STATE_UNAWARE" : L"")
    << ((state & SCARD_STATE_PRESENT)  ? 
      L" SCARD_STATE_PRESENT" : L"")
    << ((state & SCARD_STATE_ATRMATCH)  ? 
      L" SCARD_STATE_ATRMATCH" : L"")
    << ((state & SCARD_STATE_CHANGED)  ? 
      L" SCARD_STATE_CHANGED" : L"")
    << ((state & SCARD_STATE_EMPTY)  ? 
      L" SCARD_STATE_EMPTY" : L"")
    << ((state & SCARD_STATE_EXCLUSIVE)  ? 
      L" SCARD_STATE_EXCLUSIVE" : L"")
    << ((state & SCARD_STATE_IGNORE)  ? 
      L" SCARD_STATE_IGNORE" : L"")
    << ((state & SCARD_STATE_INUSE)  ? 
      L" SCARD_STATE_INUSE" : L"")
    << ((state & SCARD_STATE_MUTE)  ? 
      L" SCARD_STATE_MUTE" : L"")
    << ((state & SCARD_STATE_UNAVAILABLE)  ? 
      L" SCARD_STATE_UNAVAILABLE" : L"")
    << ((state & SCARD_STATE_UNKNOWN)  ? 
      L" SCARD_STATE_UNKNOWN" : L"")
    << ((state & SCARD_STATE_UNPOWERED)  ? 
      L" SCARD_STATE_UNPOWERED" : L"")
  ;
  return stream;
}

int wmain(int argc, WCHAR* argv[])
{
  unique_winerror winerror;
  for (;;) {
    SCARDCONTEXT context = NULL;

    HANDLE waitfor[] = {SCardAccessStartedEvent()};
    ON_UNWIND_AUTO([] {SCardReleaseStartedEvent();});

    winerror = smart_card::monitor_smartcard_readers(
      [&](SCARDCONTEXT context) {
        context = context;
      },
      [&]() {
        context = NULL;
      },
      [&]() -> bool {
        if (WAIT_OBJECT_0 != WaitForMultipleObjects(
          lib::rng::size(waitfor), 
          waitfor, 
          FALSE, 
          INFINITE)
        ) {
          // monitor_smardcard_readers will return 
          // SCARD_E_CANCELLED
          return false;
        }
        return true;
      },
      [&](
        lib::rng::range<SCARD_READERSTATE*> readersrange
      ) {
        for (auto & state : readersrange) {
          auto stateChanges = (
            (state.dwCurrentState ^ state.dwEventState) & 
              std::numeric_limits<unsigned short>::max()
          );
          std::wcout
            << L"nothread - "
            << state.szReader
            << L" changes: " 
            << std::hex 
            << std::showbase 
            << stateChanges
            << L"["
          ;
          printSCardState(std::wcout, stateChanges)
            << L"] state: " 
            << std::hex 
            << std::showbase 
            << state.dwEventState
            << L"["
          ;
          printSCardState(std::wcout, state.dwEventState)
            << L"]"
            << std::endl
          ;

          if (
            state.dwCurrentState != 
              SCARD_STATE_UNAWARE &&
            ((state.dwEventState & 
              SCARD_STATE_PRESENT) != 
                SCARD_STATE_PRESENT ||
              stateChanges == SCARD_STATE_INUSE ||
              stateChanges == SCARD_STATE_UNPOWERED ||
              (state.dwEventState & (
                SCARD_STATE_UNPOWERED | 
                SCARD_STATE_EMPTY | 
                SCARD_STATE_IGNORE | 
                SCARD_STATE_UNKNOWN | 
                SCARD_STATE_UNAVAILABLE | 
                SCARD_STATE_MUTE)) ||
            state.cbAtr == 0)
          ) {
            // we have seen this reader before 
            // and one of:
            // no card
            // only flipped INUSE
            // only flipped UNPOWERED
            // UNPOWERED EMPTY UNKNOWN UNAVAILABLE MUTE
            // no atr
            //
            // don't try to read the card
            continue;
          }

          CardWithProvider card;
          std::tie(winerror, card) = 
            smart_card::smartcard_name_and_provider(
              lib::rng::make_range_raw(state.rgbAtr)
          );
          if (!winerror) {
            continue;
          }

          KeyWithCertificateVector certificates;
          std::tie(winerror, certificates) = 
            smart_card::smartcard_certificates(
              card.kspname);
          if (!winerror) {
            continue;
          }

          std::wcout << L"nothread -"
          << L" kspname: " << card.kspname.c_str()
          << std::endl;

          printCertificates(
            lib::rng::make_range_raw(certificates)
          );
        }
      }
    );
    winerror.suppress();
  }

  return 0;
}

Thursday, April 12, 2012

smartcard_certificates implementation

Given a provider name NCryptEnumKeys can be used to retrieve all the keys known to that provider. Since the KSP provider is shared across all cards associated to it, the keys from all associated cards will be returned.

Here is a function that uses NCryptEnumKeys and NCryptGetProperty to retrieve the keys and certificates from a KSP provider name.

struct KeyWithCertificate {
  std::wstring key;
  std::vector<BYTE> cert;
};
typedef
  std::vector<KeyWithCertificate>
KeyWithCertificateVector;

inline
std::pair<unique_winerror, KeyWithCertificateVector>
smartcard_certificates(
  const std::wstring& kspstring
)
{
  unique_winerror winerror;
  KeyWithCertificateVector output;

  NCRYPT_PROV_HANDLE provider = NULL;
  ON_UNWIND_AUTO(
    [&] { 
      if (provider) {
        NCryptFreeObject(provider);
      }
    }
  );
  winerror.reset(
    NCryptOpenStorageProvider(
        &provider,
        kspstring.c_str(),
        0
    )
  );
  if (!winerror) {
    return std::make_pair(winerror, std::move(output));
  }

  NCryptKeyName* keyname = nullptr;
  ON_UNWIND_AUTO(
    [&] { 
      if (keyname) {
        NCryptFreeBuffer(keyname);
      }
    }
  );
  void* enumstate = nullptr;
  ON_UNWIND_AUTO(
    [&] { 
      if (enumstate) {
        NCryptFreeBuffer(enumstate);
      }
    }
  );
  for (;;) {
    winerror.reset(
      NCryptEnumKeys(
          provider,
          NULL,
          &keyname,
          &enumstate,
          NCRYPT_SILENT_FLAG
      )
    );
    if (winerror == winerror_cast(NTE_NO_MORE_ITEMS)) {
      winerror.suppress().release();
      break;
    }
    if (!winerror) {
      return std::make_pair(winerror, std::move(output));
    }

    KeyWithCertificate keystate;
    keystate.key = keyname->pszName;

    NCRYPT_KEY_HANDLE key = NULL;
    ON_UNWIND_AUTO(
      [&] { 
        if (key) {
          NCryptFreeObject(key);
        }
      }
    );
    winerror.reset(
      NCryptOpenKey(
          provider,
          &key,
          keyname->pszName,
          keyname->dwLegacyKeySpec,
          NCRYPT_SILENT_FLAG
      )
    );
    if (!winerror) {
      return std::make_pair(winerror, std::move(output));
    }

    DWORD sizecert = 0;
    for (bool getsize = true; ; getsize = false) {
      winerror.reset(
        NCryptGetProperty(
            key,
            NCRYPT_CERTIFICATE_PROPERTY,
            getsize ? nullptr : &keystate.cert[0],
            sizecert,
            &sizecert,
            0
        )
      );
      if (!winerror) {
        return std::make_pair(winerror, std::move(output));
      }
      if (getsize) {
        keystate.cert.resize(sizecert);
      } else {
        break;
      }
    }

    output.push_back(keystate);
  }

  return std::make_pair(winerror, std::move(output));
}

Tuesday, April 10, 2012

smartcard_name_and_provider implementation

Once the reader monitor has reported the ATR for the card in a reader, it can be used to access the card.

Windows has a model that gives card manufacturers three options for providing access to a smart card through the crypto apis.

  • KSP/CSP - the manufacturer can supply a full KSP(Key Storage Provider) and CSP (Cryptographic Service Provider) and associate it with an ATR.
  • Smart Card Minidriver - the manufacturer can supply a minidriver dll that the built-in Smart Card KSP and CSP will load to provide access and associate it with an ATR.
  • PICS/GIDS - the manufacturer can provide a card that implements the GIDS or PICS card standard. Windows has built-in minidrivers for these.

Because of the first option, once an ATR is in hand, SCardGetCardTypeProviderName must be called to retrieve the KSP or CSP provider name that should be used for the card.

SCardListCards will retrieve the card name from an ATR, which is then passed to SCardGetCardTypeProviderName. Windows only supports one KSP/CSP for an ATR, so even though SCardListCards returns a list of card names any one of them can be used to retrieve the provider names.

Here is the code to get a card name and the KSP provider name.

struct CardWithProvider {
  std::wstring cardname;
  std::wstring kspname;
};

inline
std::pair<unique_winerror, CardWithProvider>
smartcard_name_and_provider(
  lib::rng::range<const BYTE*> atr
)
{
  unique_winerror winerror;
  unique_close_scardcontext context;

  CardWithProvider output;

  winerror.reset(
    SCardEstablishContext(
        SCARD_SCOPE_USER,
        NULL,
        NULL,
        context.replace()
    )
  );
  if (!winerror) {
    return std::make_pair(winerror, std::move(output));
  }

  LPWSTR kspstring = nullptr;
  ON_UNWIND(
    unwind_kspstring,
    [&] { 
      if (kspstring) {
        SCardFreeMemory(context.get(), kspstring);
      }
    }
  );
  DWORD ksplength = SCARD_AUTOALLOCATE;

  LPWSTR cardsstring = nullptr;
  ON_UNWIND_AUTO(
    [&] { 
      if (cardsstring) {
        SCardFreeMemory(context.get(), cardsstring);
      }
    }
  );
  DWORD cardslength = SCARD_AUTOALLOCATE;

  winerror.reset(
    SCardListCards(
        context.get(),
        atr.begin(),
        NULL,
        NULL,
        reinterpret_cast<LPWSTR>(&cardsstring),
        &cardslength
    )
  );
  if (winerror == 
      winerror_cast(SCARD_E_READER_UNAVAILABLE)) {
    winerror.suppress();
    // don't free the static string we are 
    // about to assign
    unwind_kspstring.dismiss();
    kspstring = MS_SMART_CARD_KEY_STORAGE_PROVIDER;
    ksplength = wcslen(kspstring);
  } else if (!winerror) {
    return std::make_pair(winerror, std::move(output));
  } else {
    winerror.reset(
      SCardGetCardTypeProviderName(
          context.get(),
          cardsstring,
          SCARD_PROVIDER_KSP,
          reinterpret_cast<LPWSTR>(&kspstring),
          &ksplength
      )
    );
    if (!winerror) {
      return std::make_pair(winerror, std::move(output));
    }
  }

  if (cardsstring) {
    output.cardname = cardsstring;
  }
  output.kspname = kspstring;

  return std::make_pair(winerror, std::move(output));
}

Thursday, April 5, 2012

monitor_smartcard_readers implementation

The Win32 API to monitor smart card readers is SCardGetStatusChange. However its usage is sufficiently complex and arcane that a post like this is warranted to show usage.

SCardGetStatusChange blocks until a change takes place at which point it returns. An array containing state for each reader is passed in to indicate the state as understood by the caller and when the function detects a difference between the actual state and the state in the array the function updates the array with the new state and returns. The caller then processes the changes and then calls back with its new understanding.

template<
  typename SetContext, 
  typename ClearContext,
  typename Wait,
  typename Report
>
unique_winerror monitor_smartcard_readers(
  SetContext && setContext,
  ClearContext && clearContext,
  Wait && wait,
  Report &&  report
)
{
  unique_winerror winerror;

These hold the output of SCardListReaders and the input/output for SCardGetStatusChange. In fact the readers elements contain pointers into the readernames array. This is why readernames is first so that its lifetime exceeds the lifetime of readers. There is additional code to maintain this lifetime contract further down.

  std::vector<wchar_t> readernames;
  std::vector<SCARD_READERSTATE> readers;

  while (winerror) {

make sure that the smart card service has started and that the loop has not been cancelled


    if (!std::forward<Wait>(wait)()) {
      return winerror_cast(SCARD_E_CANCELLED);
    }

    monitor_error_contract(
    [&]() {

      unique_close_scardcontext context;
      ON_UNWIND_AUTO(
        [&] {
          std::forward<ClearContext>(clearContext)();
        }
      );

need a fresh context whenever we start over. lots of system changes could have caused this restart including the smart card service stopping.


      winerror.reset(
        SCardEstablishContext(
            SCARD_SCOPE_USER,
            NULL,
            NULL,
            context.replace()
        )
      );
      if (!winerror || !context) {
        return;
      }

      std::forward<SetContext>(setContext)(context.get());

make sure that the loop has not been cancelled. without this second wait there is a race where the new context is not cancelled because the caller cancelled the context at a time when there was no context.


      if (!std::forward<Wait>(wait)()) {
        winerror = winerror_cast(SCARD_E_CANCELLED);
        return;
      }

      if (readers.empty()) {

Windows allows smart card readers to be inserted and removed from the machine. In order to support these Plug-and-Play events a static reader name was defined so that SCardGetStatusChange can report PnP changes. the make function defaults the initial state to unaware which causes SCardGetStatusChange to return immediately with the actual PnP state.

        readers.push_back(
          make(L"\\\\?PnP?\\Notification")
        );
      }

      for (;;) {
        auto readersstaterange = lib::rng::make_range_raw(
          readers
        );

        winerror.reset(
          SCardGetStatusChange(
              context.get(),
              INFINITE,
              readersstaterange.begin(),
              lib::rng::size_cast<DWORD>(
                readersstaterange.size()
              )
          )
        );
        if (!winerror) {
          // exit
          return;
        }

report the smart card reader state

        auto readersrange = lib::rng::make_range_raw(
          readers, 
          0, 
          -1
        );
        if (!readersrange.empty()) {
          std::forward<Report>(report)(readersrange);
        }

record the changes now that they have been reported. setting dwCurrentState = dwEventState is required before calling SCardGetStatusChange again or the function will just return immediately to report the same changes.

        for (auto & state : readers) {
          state.dwCurrentState = state.dwEventState;
        }

        if ((
            readers.back().dwEventState & 
            SCARD_STATE_CHANGED
          ) == SCARD_STATE_CHANGED
        ) {
          // Pnp event - list readers.
          break;
        }
      }

A PnP event occured. must keep the existing allocations for use while building the new list.

      std::vector<wchar_t> oldreadernames(
        std::move(readernames)
      );
      std::vector<SCARD_READERSTATE> oldreaders(
        std::move(readers)
      );

create a range of the existing reader states that excludes the static pnp state

      auto oldreaderssortedrange = lib::rng::make_range(
        oldreaders,
        0, 
        -1
      );

      LPWSTR concatreaderstrings = nullptr;
      ON_UNWIND_AUTO(
        [&] { 
        if (concatreaderstrings) {
          SCardFreeMemory(
            context.get(), 
            concatreaderstrings
          );
        };
      }
      );
      DWORD totallength = SCARD_AUTOALLOCATE;

      winerror.reset(
        SCardListReaders(
            context.get(),
            nullptr,
            reinterpret_cast<LPWSTR>(&concatreaderstrings),
            &totallength
        )
      );

no readers is not an error, just loop around to wait for a reader to be connected

      if (winerror == 
          winerror_cast(SCARD_E_NO_READERS_AVAILABLE)
      ) {
        winerror.suppress().release();
        return;
      } else if (!winerror) {
        return;
      }

save a copy of the reader names because the state array will have pointers into them.

      readernames.assign(
        concatreaderstrings, 
        concatreaderstrings + totallength
      );

      auto readerstateless = [](
        const SCARD_READERSTATE & lhs, 
        const SCARD_READERSTATE & rhs
      ) -> bool {
        return _wcsicmp(lhs.szReader, rhs.szReader) < 0;
      };

all the reader names are concatenated in this array with embedded nulls for each and two nulls to mark the end

      auto cursorreadernames = lib::rng::make_range_raw(
        readernames
      );
      while (
        !cursorreadernames.empty() && 
        cursorreadernames.front() != L'\0'
      ) {
        // access the current name
        auto namerange = lib::rng::make_range(
          cursorreadernames,
          0,
          wcslen(
            cursorreadernames.begin()
          ) - cursorreadernames.size()
        );
        // skip to the next name
        cursorreadernames = lib::rng::make_range(
          namerange, 
          namerange.size() + 1, 
          0
        );

find this reader in the old list of readers. This list is kept sorted so that std::equal_range can be used.

        auto oldreader = std::equal_range(
          oldreaderssortedrange.begin(),
          oldreaderssortedrange.end(),
          make(namerange.begin()),
          readerstateless
        );
        if (oldreader.first != oldreader.second) {
          // keep the old state for this reader
          readers.push_back(*oldreader.first);

          // must use the new string allocation,
          // the old one will be gone soon
          readers.back().szReader = namerange.begin();
        } else {
          // this reader has not been seen yet.
          // make defaults to setting the state to 
          // unaware, so that the next call to
          // SCardGetStatusChange will return 
          // immediately with the actual state.
          readers.push_back(make(namerange.begin()));
        }
      }

keeping the reader states sorted makes the updates more stable and allows the std::equal_range above instead of a linear find.

      std::sort(
        readers.begin(), 
        readers.end(), 
        readerstateless
      );

add PnP state query to the new list. keep the existing state, and keep it at the end of the list, out of the sorted area.

      readers.push_back(oldreaders.back());
    }
    );
  }
  return winerror;
}

Tracking the readers state is only the first step. Future posts will explore tracking cards, certificates and subjects/users

Tuesday, April 3, 2012

monitor_smartcard_readers usage

Building a loop to monitor smart cards on windows turns out to be tricky. I have written a function to do this and allow code to be inserted in the necessary places.

template<
  typename SetContext, 
  typename ClearContext,
  typename Wait,
  typename Report
>
unique_winerror monitor_smartcard_readers(
  SetContext && setContext,
  ClearContext && clearContext,
  Wait && wait,
  Report &&  report
);

By itself the data produced by this function isn't too interesting, but I have a project in mind that I hope will use it to advantage.

To use monitor_smartcard_readers you call the function passing in lambdas for the code to insert.

  • setContext when the function establishes a context it calls this lambda to allow the caller to store the context and use it later to call SCardCancel
  • clearContext when the function closes the context it calls this function to inform the caller that it is no longer valid.
  • wait when the function needs to wait for the service to become available or check to see if it should exit it calls this lambda. this lambda must return a bool, true for continue and false for exit.
  • report when the function has an update to the status it calls this lambda with a range of reader states.

A quick example should show how this all stitches together.

void monitor()
{

First need to get the win32 event handle that is set when the smart card service is running.

  HANDLE waitfor[1] = {SCardAccessStartedEvent()};
  ON_UNWIND_AUTO([] {SCardReleaseStartedEvent();});

  unique_winerror winerror;
  SCARDCONTEXT context = NULL;

  while (!winerror) {
    winerror = monitor_smartcard_readers(

when setContext is called, store the context in the local stack variable.

    [&](SCARDCONTEXT context) {
      context = context;
    },

when clearContext is called, set the local stack variable to NULL

    [&]() {
      context = NULL;
    },

when wait is called, wait for the smart card service to start. If additional threads were in play this might include other events to signal that monitor_smartcard_readers should exit.

    [&]() -> bool {
      if (WAIT_OBJECT_0 != WaitForMultipleObjects(
          lib::rng::size(waitfor), 
          waitfor, 
          FALSE, 
          INFINITE
        )
      ) {
        // monitor_smardcard_readers will 
        // return SCARD_E_CANCELLED
        return false;
      }
      return true;
    },

when report is called, print out the state changes.

    [&](
      lib::rng::range<SCARD_READERSTATE*> readersrange
    ) {
      std::wcout << L"---status update---" << std::endl;
      for (auto & state : readersrange) {
        auto stateChanges = (
          state.dwCurrentState ^ state.dwEventState
        ) & std::numeric_limits<unsigned short>::max();
        
        std::wcout << L"reader: " << state.szReader 
        << L" changes: " << 
           std::hex << std::showbase << stateChanges
        << L" state: "
        << ((state.dwEventState == SCARD_STATE_UNAWARE) ? 
           L" SCARD_STATE_UNAWARE" : L"")
        << ((state.dwEventState & SCARD_STATE_PRESENT) ? 
           L" SCARD_STATE_PRESENT" : L"")
        << ((state.dwEventState & SCARD_STATE_ATRMATCH) ? 
           L" SCARD_STATE_ATRMATCH" : L"")
        << ((state.dwEventState & SCARD_STATE_CHANGED) ? 
           L" SCARD_STATE_CHANGED" : L"")
        << ((state.dwEventState & SCARD_STATE_EMPTY) ? 
           L" SCARD_STATE_EMPTY" : L"")
        << ((state.dwEventState & SCARD_STATE_EXCLUSIVE) ? 
           L" SCARD_STATE_EXCLUSIVE" : L"")
        << ((state.dwEventState & SCARD_STATE_IGNORE) ? 
           L" SCARD_STATE_IGNORE" : L"")
        << ((state.dwEventState & SCARD_STATE_INUSE) ? 
           L" SCARD_STATE_INUSE" : L"")
        << ((state.dwEventState & SCARD_STATE_MUTE) ? 
           L" SCARD_STATE_MUTE" : L"")
        << ((state.dwEventState & SCARD_STATE_UNAVAILABLE) ? 
           L" SCARD_STATE_UNAVAILABLE" : L"")
        << ((state.dwEventState & SCARD_STATE_UNKNOWN) ? 
           L" SCARD_STATE_UNKNOWN" : L"")
        << ((state.dwEventState & SCARD_STATE_UNPOWERED) ? 
           L" SCARD_STATE_UNPOWERED" : L"")
        << std::endl; 
      }
      std::wcout << L"--status update end--" << std::endl;
    }
    );
  }
}

Next post will explore the implementation of monitor_smartcard_readers..