NFC/PC_SCサンプル

Last-modified: 2013-02-04 (月) 00:52:36

PC/SC経由でPaSoRi(RC-S380)を使い、リーダーのシリアル番号とFeliCa IDmを取得するC#のプログラム例。
PC/SCのプログラミング方法はEternalWindows セキュリティ/スマートカード を参考に作成。

DllImportの指定は、PINVOKE.NETのwinscardのカテゴリを参考にした。しかし、そのサイトでもfunction間で指定が統一されておらず、当方も仕組みが分かっていない上、CもC#も不慣れなため、宣言が適切かはかなりあやしい。
その他、かなり乱雑。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Threading;

namespace SCardSample
{


    class Program
    {

        [DllImport("winscard.dll")]
        static extern uint SCardEstablishContext(uint dwScope, IntPtr pvReserved1, IntPtr pvReserved2, out IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardListReadersW", CharSet = CharSet.Unicode)]
        public static extern uint SCardListReaders(
          IntPtr hContext,
          byte[] mszGroups,
          byte[] mszReaders,
          ref UInt32 pcchReaders
          );

        [DllImport("WinScard.dll")]
        static extern uint SCardReleaseContext(IntPtr phContext);

        [DllImport("winscard.dll", EntryPoint = "SCardConnectW", CharSet = CharSet.Unicode)]
        static extern uint SCardConnect(
             IntPtr hContext,
             string szReader,
             uint dwShareMode,
             uint dwPreferredProtocols,
             ref IntPtr phCard,
             ref IntPtr pdwActiveProtocol);

        [DllImport("WinScard.dll")]
        static extern uint SCardDisconnect(IntPtr hCard, int Disposition);

        [StructLayout(LayoutKind.Sequential)]
        internal class SCARD_IO_REQUEST
        {
            internal uint dwProtocol;
            internal int cbPciLength;
            public SCARD_IO_REQUEST()
            {
                dwProtocol = 0;
            }
        }

        [DllImport("winscard.dll")]
        public static extern uint SCardTransmit(IntPtr hCard, IntPtr pioSendRequest, byte[] SendBuff, int SendBuffLen, SCARD_IO_REQUEST pioRecvRequest,
                byte[] RecvBuff, ref int RecvBuffLen);

        [DllImport("winscard.dll")]
        public static extern uint SCardControl(IntPtr hCard, int controlCode, byte[] inBuffer, int inBufferLen, byte[] outBuffer, int outBufferLen, ref int bytesReturned);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        internal struct SCARD_READERSTATE
        {
            /// <summary>
            /// Reader
            /// </summary>
            internal string szReader;
            /// <summary>
            /// User Data
            /// </summary>
            internal IntPtr pvUserData;
            /// <summary>
            /// Current State
            /// </summary>
            internal UInt32 dwCurrentState;
            /// <summary>
            /// Event State/ New State
            /// </summary>
            internal UInt32 dwEventState;
            /// <summary>
            /// ATR Length
            /// </summary>
            internal UInt32 cbAtr;
            /// <summary>
            /// Card ATR
            /// </summary>
            [MarshalAs(UnmanagedType.ByValArray, SizeConst = 36)]
            internal byte[] rgbAtr;
        }

        [DllImport("winscard.dll", EntryPoint = "SCardGetStatusChangeW", CharSet = CharSet.Unicode)]
        public static extern uint SCardGetStatusChange(IntPtr hContext, int dwTimeout, [In, Out] SCARD_READERSTATE[] rgReaderStates, int cReaders);

        [DllImport("kernel32.dll", SetLastError = true)]
        private extern static IntPtr LoadLibrary(string lpFileName);

        [DllImport("kernel32.dll")]
        private extern static void FreeLibrary(IntPtr handle);

        [DllImport("kernel32.dll")]
        private extern static IntPtr GetProcAddress(IntPtr handle, string procName);







        volatile static bool quit = false;

        static void KeyCheck(object userState)
        {
            ConsoleKeyInfo keyInfo = Console.ReadKey(true);
            quit = true;
        }

        static void Main(string[] args)
        {

            IntPtr hContext = establishContext();
            try
            {
                ThreadPool.QueueUserWorkItem(new WaitCallback(KeyCheck), null);

                List<string> readersList = getReaders(hContext);

                Debug.WriteLine(readersList.Count + "台のリーダーがあります。");
                foreach (string readerName in readersList)
                {
                    Debug.WriteLine("リーダー:" + readerName);
                }

                ServerConnector serverConnector = new ServerConnector();

                SCARD_READERSTATE[] readerStateArray = initializeReaderState(hContext, readersList);
                updateCurrentState(readerStateArray);
                while(!quit)
                {
                    const int SCARD_STATE_CHANGED  =   0x00000002;// This implies that there is a
                                            // difference between the state
                                            // believed by the application, and
                                            // the state known by the Service
                                            // Manager.  When this bit is set,
                                            // the application may assume a
                                            // significant state change has
                                            // occurred on this reader.
                    const int SCARD_STATE_PRESENT = 0x00000020;// This implies that there is a card
                                            // in the reader.
                    const int SCARD_STATE_EMPTY = 0x00000010;  // This implies that there is not
                                            // card in the reader.  If this bit
                                            // is set, all the following bits
                                            // will be clear.
                    try
                    {
                        waitReaderStatusChange(hContext, readerStateArray, 1000);
                        for (int idx = 0; idx < readerStateArray.Length; idx++)
                        {
                            uint eventState = readerStateArray[idx].dwEventState;
                            if ((eventState & SCARD_STATE_CHANGED) != 0)
                            {
                                Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " 状態が変化しました。" + String.Format("{0:X}", eventState));

                                uint changedStateMask = eventState ^ readerStateArray[idx].dwCurrentState;
                                if ((changedStateMask & (SCARD_STATE_EMPTY | SCARD_STATE_PRESENT)) != 0) {

                                    if ((eventState & SCARD_STATE_PRESENT) != 0)
                                    {
                                        Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードがセットされました。");
                                        ReadResult result = readCard(hContext, readerStateArray[idx].szReader);
                                        // result.readerSerialNumber → リーダーのシリアル番号
                                        // result.cardId   → IDm
                                    }
                                    if ((eventState & SCARD_STATE_EMPTY) != 0)
                                    {
                                        Debug.WriteLine("リーダー " + readerStateArray[idx].szReader + " カードが外されました。");
                                    }
                                }
                            }
                        }
                        updateCurrentState(readerStateArray);
                    }
                    catch (TimeoutException e)
                    {
                        // 無視
                    }

                }

            }
            finally
            {
                uint ret = SCardReleaseContext(hContext);
            }
        }

        static ReadResult readCard(IntPtr hContext, string readerName)
        {
            IntPtr hCard = connect(hContext, readerName);
            string readerSerialNumber = readReaderSerialNumber(hCard);
            string cardId = readCardId(hCard);
            Debug.WriteLine(readerName + " (S/N " + readerSerialNumber + ") から、カードを読み取りました。" + cardId);
            disconnect(hCard);

            ReadResult result = new ReadResult();
            result.readerSerialNumber = readerSerialNumber;
            result.cardId = cardId;
            return result;

        }



        static string readCardId(IntPtr hCard)
        {
            byte maxRecvDataLen = 64;
            byte[] recvBuffer = new byte[maxRecvDataLen + 2];
            byte[] sendBuffer = new byte[] { 0xff, 0xca, 0x00, 0x00, maxRecvDataLen };
            int recvLength = transmit(hCard, sendBuffer, recvBuffer);

            // recvBuffer の最後の2バイトはステータスコードなので、応じた処理が必要。
            // http://eternalwindows.jp/security/scard/scard07.html

            string cardId = BitConverter.ToString(recvBuffer, 0, recvLength - 2).Replace("-", "");
            return cardId;
        }

        static string readReaderSerialNumber(IntPtr hCard)
        {
            int controlCode = 0x003136b0; // SCARD_CTL_CODE(3500) の値
                                    // IOCTL_PCSC_CCID_ESCAPE
                                    // SONY SDK for NFC M579_PC_SC_2.1j.pdf 3.1.1 IOCTRL_PCSC_CCID_ESCAPE
            byte[] sendBuffer = new byte[] {0xc0, 0x08 }; // ESC_CMD_GET_INFO / Product Serial Number
            byte[] recvBuffer = new byte[64];
            int recvLength = control(hCard, controlCode, sendBuffer, recvBuffer);

            ASCIIEncoding asciiEncoding = new ASCIIEncoding();
            string serialNumber = asciiEncoding.GetString(recvBuffer, 0, recvLength - 1); // recvBufferには\0で終わる文字列が取得されるので、長さを-1する。
            return serialNumber;
        }



        const uint SCARD_S_SUCCESS = 0;
        const uint SCARD_E_NO_SERVICE = 0x8010001D;
        const uint SCARD_E_TIMEOUT = 0x8010000A;

        static IntPtr establishContext()
        {
            IntPtr hContext = IntPtr.Zero;
            const uint SCARD_SCOPE_USER = 0;
            const uint SCARD_SCOPE_TERMINAL = 1;
            const uint SCARD_SCOPE_SYSTEM = 2;

            uint ret = SCardEstablishContext(SCARD_SCOPE_USER, IntPtr.Zero, IntPtr.Zero, out hContext);
            if (ret != SCARD_S_SUCCESS)
            {
                string message;
                switch (ret)
                {
                    case SCARD_E_NO_SERVICE:
                        message = "Smart Cardサービスが起動されていません。";
                        break;
                    default:
                        message= "Smart Cardサービスに接続できません。code = " + ret;
                        break;
                }
                Debug.WriteLine(message);
                throw new NotSupportedException(message);
            }
            Debug.WriteLine("Smart Cardサービスに接続しました。");
            return hContext;
        }

        static List<string> getReaders(IntPtr hContext)
        {
            uint pcchReaders = 0;

            // First call with 3rd parameter set to null gets readers buffer length.
            uint ret = SCardListReaders(hContext, null, null, ref pcchReaders);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
            }

            byte[] mszReaders = new byte[pcchReaders * 2]; // 1文字2byte

            // Fill readers buffer with second call.
            ret = SCardListReaders(hContext, null, mszReaders, ref pcchReaders);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの情報が取得できません。code = " + ret);
            }

            UnicodeEncoding unicodeEncoding = new UnicodeEncoding();
            string readerNameMultiString = unicodeEncoding.GetString(mszReaders);

            Debug.WriteLine("リーダー名を\\0で接続した文字列: " + readerNameMultiString);
            Debug.WriteLine(" ");

            int len = (int)pcchReaders;
            char nullchar = (char)0;
            List<string> readersList = new List<string>();

            if (len > 0)
            {
                while (readerNameMultiString[0] != nullchar)
                {
                    int nullindex = readerNameMultiString.IndexOf(nullchar);   // Get null end character.
                    string readerName = readerNameMultiString.Substring(0, nullindex);
                    readersList.Add(readerName);
                    len = len - (readerName.Length + 1);
                    readerNameMultiString = readerNameMultiString.Substring(nullindex + 1, len);
                }
            }
            return readersList;
        }


        static IntPtr connect(IntPtr hContext, string readerName)
        {
            const int SCARD_SHARE_SHARED = 0x00000002; // - This application will allow others to share the reader
            const int SCARD_SHARE_EXCLUSIVE = 0x00000001; // - This application will NOT allow others to share the reader
            const int SCARD_SHARE_DIRECT = 0x00000003; // - Direct control of the reader, even without a card


            const int SCARD_PROTOCOL_T0= 1; // - Use the T=0 protocol (value = 0x00000001)
            const int SCARD_PROTOCOL_T1= 2;// - Use the T=1 protocol (value = 0x00000002)
            const int SCARD_PROTOCOL_RAW = 4;// - Use with memory type cards (value = 0x00000004)

            IntPtr hCard = IntPtr.Zero;
            IntPtr activeProtocol = IntPtr.Zero;
            uint ret = SCardConnect(hContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T1, ref hCard, ref activeProtocol);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードに接続できません。code = " + ret);
            }
            return hCard;

        }

        static void disconnect(IntPtr hCard)
        {
            const int SCARD_LEAVE_CARD =     0; // Don't do anything special on close
            const int SCARD_RESET_CARD =     1; // Reset the card on close
            const int SCARD_UNPOWER_CARD =   2; // Power down the card on close
            const int SCARD_EJECT_CARD = 3; // Eject the card on close

            uint ret = SCardDisconnect(hCard, SCARD_LEAVE_CARD);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードとの接続を切断できません。code = " + ret);
            }
        }

        private static IntPtr getPciT1()
        {
            IntPtr handle = LoadLibrary("Winscard.dll");
            IntPtr pci = GetProcAddress(handle, "g_rgSCardT1Pci");
            FreeLibrary(handle);
            return pci;
        }

        static int transmit(IntPtr hCard, byte[] sendBuffer, byte[] recvBuffer)
        {
            SCARD_IO_REQUEST ioRecv = new SCARD_IO_REQUEST();
            ioRecv.cbPciLength = 255;

            int pcbRecvLength = recvBuffer.Length;
            int cbSendLength = sendBuffer.Length;
            IntPtr SCARD_PCI_T1 = getPciT1();
            uint ret = SCardTransmit(hCard, SCARD_PCI_T1, sendBuffer, cbSendLength, ioRecv, recvBuffer, ref pcbRecvLength);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードへの送信に失敗しました。code = " + ret);
            }
            return pcbRecvLength; // 受信したバイト数(recvBufferに受け取ったバイト数)

        }

        static int control(IntPtr hCard, int controlCode, byte[] sendBuffer, byte[] recvBuffer)
        {
            int bytesReturned = 0;
            uint ret = SCardControl(hCard, controlCode, sendBuffer, sendBuffer.Length, recvBuffer, recvBuffer.Length, ref bytesReturned);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("カードへの制御命令送信に失敗しました。code = " + ret);
            }
            return bytesReturned;
        }

        static SCARD_READERSTATE[] initializeReaderState(IntPtr hContext, List<string> readerNameList)
        {
            const int SCARD_STATE_UNAWARE = 0x00000000;

            SCARD_READERSTATE[] readerStateArray = new SCARD_READERSTATE[readerNameList.Count];
            int i = 0;
            foreach (string readerName in readerNameList)
            {
                readerStateArray[i].dwCurrentState = SCARD_STATE_UNAWARE;
                readerStateArray[i].szReader = readerName;
                i++;
            }


            uint ret = SCardGetStatusChange(hContext, 100/*msec*/, readerStateArray, readerStateArray.Length);
            if (ret != SCARD_S_SUCCESS)
            {
                throw new ApplicationException("リーダーの初期状態の取得に失敗。code = " + ret);
            }
            return readerStateArray;
        }

        static void waitReaderStatusChange(IntPtr hContext, SCARD_READERSTATE[] readerStateArray, int timeoutMillis)
        {
            uint ret = SCardGetStatusChange(hContext, timeoutMillis/*msec*/, readerStateArray, readerStateArray.Length);
            switch(ret) {
                case SCARD_S_SUCCESS:
                    break;
                case SCARD_E_TIMEOUT:
                    throw new TimeoutException();
                default:
                    throw new ApplicationException("リーダーの状態変化の取得に失敗。code = " + ret);
            }

        }

        static void updateCurrentState(SCARD_READERSTATE[] readerStateArray)
        {
            for (int i = 0; i < readerStateArray.Length; i++)
            {
                readerStateArray[i].dwCurrentState = readerStateArray[i].dwEventState;
            }
        }



    }


    class ReadResult
    {
        public string readerSerialNumber;
        public string cardId;
    }

}