How to Use Liblogicalaccess

Adrien JUND edited this page Aug 10, 2017 · 1 revision

Bellow are basic How To for LibLogicalAccess. You should also take a look on the unit tests.

Your project will at least requires the following include files:

#include <logicalaccess/dynlibrary/librarymanager.hpp>
#include <logicalaccess/readerproviders/readerconfiguration.hpp>
#include <logicalaccess/cards/chip.hpp>

Initialize a reader connection

The first step to do for any operation is to initialize the reader communication. The uses of a Reader Configuration object allow you to serialize/unserialize the reader choice in an easy way to reuse it later.

// Reader configuration object to store reader provider and reader unit selection.
std::shared_ptr<logicalaccess::ReaderConfiguration> readerConfig(new logicalaccess::ReaderConfiguration());
 
// Set PCSC ReaderProvider by calling the Library Manager which will load the function from the corresponding plug-in
readerConfig->setReaderProvider(logicalaccess::LibraryManager::getInstance()->getReaderProvider("PCSC"));

// Create the default reader unit. On PC/SC, we will listen on all readers.
readerConfig->setReaderUnit(readerConfig->getReaderProvider()->createReaderUnit());
if (readerConfig->getReaderProvider()->getRPType() == "PCSC" && readerConfig->getReaderProvider()->getReaderList().size() == 0)
{
  std::cerr << "No readers on this system." << std::endl;
  return EXIT_FAILURE;
}
std::cout << readerConfig->getReaderProvider()->getReaderList().size() << " readers on this system." << std::endl;

readerConfig->getReaderUnit()->connectToReader();

// DO CARD STUFF HERE
// DO CARD STUFF HERE
// DO CARD STUFF HERE

readerConfig->getReaderUnit()->disconnectFromReader();

Communicate with a chip

Once connected to the reader, we want to communicate with the chip. We wait a card insertion on the reader, get the associated chip object and we are ready to send command to it.

std::cout << "Waiting 15 seconds for a card insertion..." << std::endl;
if (readerConfig->getReaderUnit()->waitInsertion(15000))
{
       if (readerConfig->getReaderUnit()->connect())
       {
             // Display few chip information

             std::cout << "Card inserted on reader \"" << readerConfig->getReaderUnit()->getConnectedName() << "\"." << std::endl;
 
             std::shared_ptr<logicalaccess::Chip> chip = readerConfig->getReaderUnit()->getSingleChip();
             std::cout << "Card type: " << chip->getCardType() << std::endl;

             std::vector<unsigned char> csn = readerConfig->getReaderUnit()->getNumber(chip);
             std::cout << "Card unique manufacturer number : " << logicalaccess::BufferHelper::getHex(csn) << std::endl;

             // DO CHIP COMMAND HERE
             // DO CHIP COMMAND HERE
             // DO CHIP COMMAND HERE

             readerConfig->getReaderUnit()->disconnect();
       }

       std::cout << "Logical automatic card removal in 15 seconds..." << std::endl;
       if (!readerConfig->getReaderUnit()->waitRemoval(15000))
       {
             std::cerr << "Card removal forced." << std::endl;
       }

       std::cout << "Card removed." << std::endl;
} 
else
{
       std::cout << "No card inserted." << std::endl;
}

Read/Write data on the chip

Once connected to the chip, you can execute some commands like read and write data for example. A generic layer, available for all memory chips, can be used to set data location (where is the data) and data access information (authentication key).

Add this include #include <logicalaccess/services/storage/storagecardservice.hpp>.

std::shared_ptr<logicalaccess::StorageCardService> storage = std::dynamic_pointer_cast<logicalaccess::StorageCardService>(chip->getService(logicalaccess::CST_STORAGE));

std::shared_ptr<logicalaccess::Location> location;
std::shared_ptr<logicalaccess::AccessInfo> aiToUse;
std::shared_ptr<logicalaccess::AccessInfo> aiToWrite;

You should define location and access information according to the targeting chip. See Mifare Classic examples for an example with Mifare Classic. Then use it:

// Data to write
std::vector<uint8_t> writedata(16, 'a');

// Data read
std::vector<uint8_t> readdata;

// Write data on the specified location with the specified key
storage->writeData(location, aiToUse, aiToWrite, writedata, logicalaccess::CB_DEFAULT);

// We read the data on the same location. Remember, the key is now changed.
readdata = storage->readData(location, aiToWrite, 16, logicalaccess::CB_DEFAULT);
std::string str(readdata.begin(), readdata.end());

You can see https://github.com/islog/liblogicalaccess/blob/develop/tests/pcsc-tests/mifare1k/test_storage_service.cpp

Access control chip service

std::shared_ptr<logicalaccess::Format> format;

/* -------------------------------- */
/* Example with Wiegand 26 format   */
/* -------------------------------- */

std::shared_ptr<logicalaccess::Wiegand26Format> w26format(new logicalaccess::Wiegand26Format());
format = w26format;

/* -------------------------------- */
/* -------------------------------- */
/* -------------------------------- */

std::shared_ptr<logicalaccess::AccessControlCardService> acService = std::dynamic_pointer_cast<logicalaccess::AccessControlCardService>(chip->getService(logicalaccess::CST_ACCESS_CONTROL));

Write access control format

/* -------------------------------- */
/* Example with Wiegand 26 format   */
/* -------------------------------- */

w26format->setFacilityCode(50);
w26format->setUid(1000);

/* -------------------------------- */
/* -------------------------------- */
/* -------------------------------- */

// Write the format to the card. We use object from the previous snippet code.
acService->writeFormat(format, location, aiToUse, aiToWrite);

Read access control format

// Read the format from the card. We use object from the previous snippet code.
std::shared_ptr<logicalaccess::Format> retformat = acService->readFormat(format, location, aiToUse);

// Some chip access control service implementation could change the returned format.
// It is recommended to not use the given format in argument, but the returned one with appropriate tests.

/* -------------------------------- */
/* Example with Wiegand 26 format   */
/* -------------------------------- */

std::shared_ptr<logicalaccess::Wiegand26Format> w26retformat = std::dynamic_pointer_cast<logicalaccess::Wiegand26Format>(retformat);

if (w26retformat)
{
    std::cout << "Successfully read Wiegand 26 format. Uid: " << w26retformat->getUid() << std::endl;
}

/* -------------------------------- */
/* -------------------------------- */
/* -------------------------------- */

Low level chip command

The chip’s commands can be casted to the associated implementation to access low level chip command. The same logic can be applied to other generic object (reader provider, reader unit, chip, profile, card provider, location, access info, format ...).

/* -------------------------------- */
/* Example with DESFire chip        */
/* -------------------------------- */

std::shared_ptr<logicalaccess::DESFireCommands> desfirecmd = std::dynamic_pointer_cast<logicalaccess::DESFireCommands>(chip->getCommands());
 
// Select the AID 0x521
desfirecmd->selectApplication(0x521);
 
/* -------------------------------- */
/* -------------------------------- */
/* -------------------------------- */

Handle exception

try
{
  // DO CONTACTLESS STUFF HERE
  // DO CONTACTLESS STUFF HERE
  // DO CONTACTLESS STUFF HERE
}
catch(logicalaccess::LibLogicalAccessException& ex)
{
  std::cout << "LibLogicalAccess exception: " << ex.what() << std::endl;
}

Iterate the chip memory areas

// Get the root node
std::shared_ptr<logicalaccess::LocationNode> rootNode = chip->getRootLocationNode();
// Get childrens of this node
std::vector<std::shared_ptr<logicalaccess::LocationNode> > childs = rootNode->getChildrens();
for (std::vector<std::shared_ptr<logicalaccess::LocationNode> >::iterator i = childs.begin(); i != childs.end(); ++i)
{
  // Display node information
  std::cout << "Size of node " << (*i)->getName() << ": " << ((*i)->getLength() * (*i)->getUnit()) << " bytes";
 
  //Get the associated location for that node (to read or write data for example)
  std::shared_ptr<logicalaccess::Location> childlocation = (*i)->getLocation();
}

Create your own Library for LibLogicalAccess

Each Reader and Card is a Library with LibLogicalAccess. If you want to add your own Reader or Card, you just have to build a Library with Library name and entry well named.

Library Name:

  • Readers.[dll/so] : This Library creates Readers.
  • Cards.[dll/so]: This Library creates Cards.
  • Unified.[dll/so]: This Library can create Readers and Cards.

For example, if you want a library named Foo who create Cards in windows: FooCards.dll

Library Entry Name:

  • getReader: This Library entry will create the Reader.
  • getChip: This Library entry will create the Chip. Example with the Library Foo with the Reader Foo2K
extern "C"
{
	LIBLOGICALACCESS_API char *getLibraryName()
	{
		return (char *)"Foo";
	}

	LIBLOGICALACCESS_API void getFoo2KReader(std::shared_ptr<logicalaccess::Foo2KReaderProvider>* rp)
	{
		 if (rp != NULL)
		 {
		 	 *rp = logicalaccess::Foo2KReaderProvider::getSingletonInstance();
		 }
	}

	LIBLOGICALACCESS_API bool getReaderInfoAt(unsigned int index, char* readername, size_t readernamelen, void** getterfct)
	{
		bool ret = false;
		if (readername != NULL && readernamelen == PLUGINOBJECT_MAXLEN && getterfct != NULL)
		{
			switch (index)
			{
			case 0:
				{
					*getterfct = (void*)&getFoo2KReader;
					sprintf(readername, READER_FOO2K);
					ret = true;
				}
				break;
			}
		}

		return ret;
	}
}

Sample code that use LibLogicalAccess and who wants the Foo2K Reader Provider.

std::shared_ptr<logicalaccess::ReaderProvider> foo2kprovider = logicalaccess::LibraryManager::getInstance()->getReaderProvider("Foo2K");
//...

Read Generic/Custom Card Fields

#include "logicalaccess/dynlibrary/idynlibrary.hpp"
#include "logicalaccess/dynlibrary/librarymanager.hpp"
#include "logicalaccess/readerproviders/readerconfiguration.hpp"
#include "logicalaccess/services/storage/storagecardservice.hpp"
#include "logicalaccess/services/accesscontrol/readerformatcomposite.hpp"
#include "logicalaccess/services/accesscontrol/formats/customformat/numberdatafield.hpp"
#include "logicalaccess/services/accesscontrol/formats/customformat/stringdatafield.hpp"
#include "logicalaccess/services/accesscontrol/formats/customformat/binarydatafield.hpp"

#include <iostream>
#include <string>
#include <iomanip>
#include <sstream>
#include <stdlib.h>


std::string ToLower(std::string data)
{
	std::string ret;
	ret.resize(data.size());
	std::transform(data.begin(), data.end(), ret.begin(), ::tolower);
	return ret;
}


/**
 * \brief The application entry point.
 * \param argc The arguments count.
 * \param argv The arguments.
 */
int main(int , char**)
{
	try
	{

		std::ifstream config;
		std::string configxml;

		config.open("config.xml");
		if (!config.is_open())
			return -1;
		while (!config.eof())
		{
			std::string tmp;
			getline(config, tmp);
			configxml += tmp;
		}
		config.close();
		std::cout << configxml << std::endl;

		std::shared_ptr<logicalaccess::ReaderFormatComposite> readerformatconfig(new logicalaccess::ReaderFormatComposite());
		std::dynamic_pointer_cast<logicalaccess::XmlSerializable>(readerformatconfig)->unSerialize(configxml, "");

		std::shared_ptr<logicalaccess::ReaderConfiguration> readerConfig(readerformatconfig->getReaderConfiguration());
		std::shared_ptr<logicalaccess::ReaderProvider> readerProvider(readerConfig->getReaderProvider());
		std::shared_ptr<logicalaccess::ReaderUnit> readerUnit(readerConfig->getReaderUnit());
		std::shared_ptr<logicalaccess::CardsFormatComposite> cardsFormatComposite;

		if (!readerUnit)
		{
			readerUnit = readerProvider->createReaderUnit();
		}
		cardsFormatComposite = readerformatconfig->getCardsFormatComposite();

		while (1)
		{
			readerUnit->connectToReader();
			std::cout << "Time start : " << time(NULL) << std::endl;
			if (readerUnit->waitInsertion(15000))
			{
				if (readerUnit->connect())
				{
					std::cout << "Card inserted on reader \"" << readerConfig->getReaderUnit()->getConnectedName() << "\"." << std::endl;
					std::shared_ptr<logicalaccess::Chip> chip = readerConfig->getReaderUnit()->getSingleChip();
					if (chip)
					{
						std::cout << "Card type: " << chip->getCardType() << std::endl;
						if (cardsFormatComposite)
						{
							std::shared_ptr<logicalaccess::Format> format;
							std::shared_ptr<logicalaccess::Location> location;
							std::shared_ptr<logicalaccess::AccessInfo> accessinfo, aiwrite;

							logicalaccess::CardTypeList cardlist = cardsFormatComposite->getConfiguredCardTypes();
							for (logicalaccess::CardTypeList::iterator it = cardlist.begin(); it < cardlist.end(); ++it)
							{
								if (chip->getCardType() ==	*it || chip->getGenericCardType() == *it || ToLower(*it).find("generic") == 0)
								{
									std::vector<unsigned char> number = readerUnit->getNumber(chip);
									std::cout << "CSM: " << logicalaccess::BufferHelper::getHex(number) << std::endl;
									std::shared_ptr<std::map<std::string, std::string> > valuesToSend(new std::map<std::string, std::string>);

									cardsFormatComposite->retrieveFormatForCard(*it, &format, &location, &accessinfo, &aiwrite);

									if (format)
									{
										cardsFormatComposite->setReaderUnit(readerUnit);
										std::shared_ptr<logicalaccess::Format> readFormat = cardsFormatComposite->readFormat();

										if (readFormat)
										{
											std::list<std::shared_ptr<logicalaccess::DataField> > fields = readFormat->getFieldList();

											for (std::list<std::shared_ptr<logicalaccess::DataField> >::iterator itdata = fields.begin(); itdata != fields.end(); ++itdata)
											{
												if (std::dynamic_pointer_cast<logicalaccess::NumberDataField>(*itdata))
												{
													std::shared_ptr<logicalaccess::NumberDataField> ndf = std::dynamic_pointer_cast<logicalaccess::NumberDataField>(*itdata);
													
													std::string tmp;
													std::stringstream strstream;
													strstream << ndf->getValue();
													strstream >> tmp;
													std::cout << "NumberDataField: " << tmp << std::endl;
													valuesToSend->insert(std::pair<std::string, std::string>(ToLower(ndf->getName()), tmp));
												}
												else if (std::dynamic_pointer_cast<logicalaccess::BinaryDataField>(*itdata))
												{
													std::shared_ptr<logicalaccess::BinaryDataField> bdf = std::dynamic_pointer_cast<logicalaccess::BinaryDataField>(*itdata);
													std::vector<unsigned char> array = bdf->getValue();
													if (array.size() > 0)
													{
														std::string str(array.begin(), array.end());
														std::cout  << "BinaryDataField: " << str << std::endl;
														valuesToSend->insert(std::pair<std::string, std::string>(bdf->getName(), str));
													}
												}
												else if (std::dynamic_pointer_cast<logicalaccess::StringDataField>(*itdata))
												{
													std::shared_ptr<logicalaccess::StringDataField> adf = std::dynamic_pointer_cast<logicalaccess::StringDataField>(*itdata);
													valuesToSend->insert(std::pair<std::string, std::string>(adf->getName(), adf->getValue()));
													std::cout << "StringDataField: " << adf->getValue() << std::endl;
												}
											}
										}
									}
								}
							}
						}
						
					}

					readerUnit->disconnect();
				}
				else
				{
					std::cout << "Error: cannot connect to the card." << std::endl;
				}

				std::cout << "Logical automatic card removal in 15 seconds..." << std::endl;

				if (!readerConfig->getReaderUnit()->waitRemoval(15000))
				{
					std::cerr << "Card removal forced." << std::endl;
				}

				std::cout << "Card removed." << std::endl;
			} else
			{
				std::cout << "No card inserted." << std::endl;
			}
		}
	}
	catch (std::exception& ex)
	{
		std::cout << ex.what() << std::endl;
	}

	return EXIT_SUCCESS;
}