-1

I'm trying to look into smart card reading and I've immediately encountered an issue that I'm struggling with. I've called the SCardEstablishContext and I'm now trying to list the cards readers. I'm getting success and Its telling me there are 89 bytes worth of string data but the string isn't being written out to me. I'm not sure what the issue could be, my code is pretty much identical to the example code on msdn only I've gotten some memory using virtualalloc for the string to.

I've tried changing types and connecting, disconnecting smart card reader, restarting the system etc etc stepping through in the debugger for each and every change attempted. Same issue, either in getting 89 empty bytes or im hitting my asserts for not succeeding at getting the context or so on.


#include <Windows.h>
#include <stdint.h>
#include <winscard.h>
#include <stdio.h>

typedef uint8_t  u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64; 
typedef uint32_t b32; 
typedef int8_t  i8;
typedef int16_t i16;
typedef int32_t i32;
typedef int64_t i64; 
#define l_persist static
#define gvar static 
#define internal static

#define KiloBytes(Value) ((Value) * 1024LL)
#define MegaBytes(Value) (KiloBytes(Value) * 1024LL)

#if INTERNAL
#define Assert(exp) if (!(exp)) {*((u8*)0) = 0;}
#define AssertWithMessage(exp, message) if (!(exp)) {OutputDebugString("\nAssertion failed: "); OutputDebugString(message); OutputDebugString("\n"); Assert(exp);} 

#else
#define Assert(exp) 
#define AssertWithMessage(exp, message)  
#endif 

#define U32_MAX 0xFFFFFFFF

b32 G_IsRunning = true;

LRESULT CALLBACK MainWindowCallback(HWND Window, UINT  Message, WPARAM  WParam, LPARAM  LParam)
{
    LRESULT Result = 0;

    switch(Message)
    {
        case WM_CLOSE:
        case WM_QUIT:
        case WM_DESTROY:
        {
            G_IsRunning = false;
        } break;

        default:
        {
            Result = DefWindowProc(Window, Message, WParam, LParam);
        }
    }

    return Result;
}

void MessagePump(HWND Window)
{
    MSG Message = {};

    u32 InputCount = 0;
    while(PeekMessage(&Message, Window, 0, 0, PM_REMOVE))
    {
        switch(Message.message)
        {
            case WM_QUIT:
            {
                G_IsRunning = false;
            } break;

            default:
            {
                TranslateMessage(&Message);
                DispatchMessage(&Message);
            } break;
        }
    }
}

    INT CALLBACK
WinMain(HINSTANCE Instance, HINSTANCE PrevInstance, LPSTR CommandLine, INT ShowCommand)
{
    WNDCLASS WindowClass = {};

    WindowClass.style = CS_OWNDC |  CS_HREDRAW | CS_VREDRAW;
    WindowClass.lpfnWndProc = MainWindowCallback;
    WindowClass.hInstance = Instance;
    WindowClass.lpszClassName = "MainWindowClass";

    if (RegisterClass(&WindowClass))
    {
        HWND Window = CreateWindowEx(0,WindowClass.lpszClassName, "Main Window", WS_OVERLAPPEDWINDOW|WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,0,0,Instance, 0);

        if (Window)
        {
            SCARDCONTEXT CardContext = U32_MAX;
            LONG CardContextSuccess = SCardEstablishContext(SCARD_SCOPE_USER, 0, 0, &CardContext);

            Assert(CardContextSuccess == SCARD_S_SUCCESS);

            u8 *Memory = (u8*)VirtualAlloc(0, KiloBytes(1), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
            u8 *MemoryHead = Memory;
            LPSTR ReadersList = (char*)MemoryHead;

            LPSTR ReadersListWip = 0;
            DWORD ReadersListSize = 0;
            LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, (LPSTR)&ReadersList, &ReadersListSize);

            Assert(ReadersList);
            Assert(ReadersListSize);
            switch(CardReadersListStatus)
            {
                case SCARD_S_SUCCESS: 
                {
                    ReadersListWip = ReadersList;
                    while ( '\0' != *ReadersListWip )
                    {
                        printf("Reader: %S\n", (wchar_t*)ReadersListWip );
                        // Advance to the next value.
                        ReadersListWip = ReadersListWip + wcslen((wchar_t *)ReadersListWip) + 1;
                    }
                } break;

                case SCARD_E_NO_READERS_AVAILABLE:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "There were no card readers available");
                    G_IsRunning = false;
                } break;

                case SCARD_E_READER_UNAVAILABLE:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "The card reader was unavailable");
                    G_IsRunning = false;
                } break;
                default:
                {
                    AssertWithMessage(CardReadersListStatus == SCARD_S_SUCCESS, "Some other issue with card reader listing");
                    G_IsRunning = false;
                }
            }

            while(G_IsRunning)
            {
                MessagePump(Window);

                LPSCARDHANDLE CardHandle = 0;
                DWORD ActiveProtocol = 0;
                LONG CardConnectStatus =  SCardConnectA(CardContext, ReadersList, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0, (LPSCARDHANDLE)&CardHandle, &ActiveProtocol);

                Assert(CardConnectStatus == SCARD_S_SUCCESS);
            }
        }
    }


    return 0;
}

Above is the code as it stands after my last batch of tests. I am quite rusty but I can't see why I'm getting a size back but not getting the strings.

Is there an alternative api for card reading? I recognize that the loop is no good at the end there that was an artefact left from some previous efforts earlier on. I'm stepping through it anyway so I'm ignoring it for now.

Thanks to Raymond Chen's comments I've realised my errors and can progress in learning the SCARD api. Mostly my mistake, but I feel that the documentation on MSDN could have been clearer. Particularly in regards to the third argument to SCardListReaders() which is an in/out parameter. I did not realise that setting the parameter to zero would return the size and then would require a second call to the same function with the buffer of sufficient length. I failed to notice the SCARD_AUTOALLOCATE section of the documentation. Not entirely sure how I manage to overlook that but I somehow did. I found this function quite confusing for those reasons.

eternalNoob
  • 307
  • 2
  • 10
  • 3
    `(LPSTR)&ReadersList` tells `SCardListReadersA` to write the list of readers into the memory starting at the `ReaderList` variable, which will corrupt the variable and smash the stack. You meant to pass just `ReadersList`. But since `ReadersListSize` is zero, it will merely report the size required and not actually return any strings. You say that your code is nearly identical to "example code on msdn". Where is that sample code? I'm surprised it has so many errors, and I'd like to fix the sample code. – Raymond Chen Dec 17 '22 at 00:56
  • (Also, the only function you mentioned in the original question was SCardGetContext, but there is no such call in your code.) – Raymond Chen Dec 17 '22 at 00:58
  • Code was here: https://learn.microsoft.com/en-us/windows/win32/api/winscard/nf-winscard-scardlistreadersa I misremembered the function name. It was SCardEstablishContext I meant to say. LPTSTR pmszReaders = NULL; LPTSTR pReader; LONG lReturn, lReturn2; DWORD cch = SCARD_AUTOALLOCATE; // Retrieve the list the readers. // hSC was set by a previous call to SCardEstablishContext. lReturn = SCardListReaders(hSC, NULL, (LPTSTR)&pmszReaders, &cch ); – eternalNoob Dec 17 '22 at 00:58
  • 2
    That code is nothing like your code. You said that the only thing you changed was using VirtualAlloc for memory allocation, but really, it's all different. You initialized the `ReadersListSize` differently, you added a GUI window (which is not in the original)... – Raymond Chen Dec 17 '22 at 01:00
  • My error is the SCARD_AUTOALLOCATE isn't it. I've only just noticed that. I overlooked that. I was using another source for finding which functions I needed and must have gotten mixed up between the code samples. I don't recall encountering an in/out parameter in the windows api's before. – eternalNoob Dec 17 '22 at 01:03
  • I'm now getting the string. Thanks Raymond. I see where I've gone wrong. – eternalNoob Dec 17 '22 at 01:10
  • Your code also leaks a lot of memory, but that's a separate problem. You made the mistake of changing too much at once. Also, the way to say that the problem is solved is not to edit the question, but rather to post an answer. This lets the system know that the problem is solved, and people with the same problem in the future can find your solution. – Raymond Chen Dec 17 '22 at 01:44
  • Yes, I had an hour or so spare and started trying to check out the card reader api. I then made the mistake of not reading the documentation carefully enough. I'm aware of most issues in the code and I introduced some new ones while trying to get it working. I did not realise it would require the second call to the same function when providing the buffer myself. It was a little confusing to me as its my first time encountering that and I didn't see anything about it on the docs. I appreciate your help. – eternalNoob Dec 17 '22 at 01:45

1 Answers1

0

Credit for helping me realise my errors goes to the infamous Raymond Chen.

Below I've written some annotated demonstration code for anyone in future who might find the api confusing as I found earlier this evening.

    LPSTR ReadersList; //im no longer initializing to zero here because a null argument tells the function to just return the size of the string(s).
#define one_call_version 0
#if one_call_version
    DWORD ReadersListSize = SCARD_AUTOALLOCATE; //passing this will cause the function to allocate some memory for you and return the strings inside that block of memory.
    LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, (LPSTR)&ReadersList, &ReadersListSize);
            //ReadersListSize now points to a populate block of memory that needs to be frees via SCardFreeMemory or process termination.
#else //ReadersList = (LPSTR)calloc(ReadersListSize, sizeof(char)); //ReadersList = (LPSTR)malloc(ReadersListSize);

    DWORD ReadersListSize = 0;
    LONG CardReadersListStatus = SCardListReadersA(CardContext, 0, 0, &ReadersListSize); //1ST// call with null 3rd argument will just get the size of the readers list string
    //ReadersListSize should now be modified to contain the minimum required size of memory allocation to contain the string(s) in bytes.
    ReadersList = (LPSTR)VirtualAlloc(0, ReadersListSize, MEM_COMMIT|MEM_RESERVE, PAGE_READWRITE);
    CardReadersListStatus = SCardListReaders(CardContext, 0, ReadersList, &ReadersListSize); //2ND// call with the buffer of sufficient size for the string(s) as 3rd argument should populate the buffer

    
#endif          //Should now have the ReadersList populated with the names of the connected smart card reader devices.
eternalNoob
  • 307
  • 2
  • 10