Parses AAMVA data encoded in PDF417 barcodes.
#include "ltbar.h"
L_LTBAR_API L_INT L_BarCodeParseAAMVAData(barCodeData, nDataLength, pId, bStrictMode)
Pointer to a character string that contains the barcode data.
The barcode data size.
Pointer to the AAMVAID structure that will be created.
Indicates whether the strict mode will be used. Possible values are:
Value | Meaning |
---|---|
L_TRUE | Use strict mode. |
L_FALSE | Don't use strict mode. |
Value | Meaning |
---|---|
SUCCESS | The function was successful. |
< 1 | An error occurred. Refer to Return Codes. |
The input data is ASCII encoded.
The strict mode enforces length and character type restrictions on data element values as outlined in the AAMVA CDS.
It is almost always recommended to pass L_FALSE to bStrictMode as many card issuing authorities break AAMVA CDS guidelines, while L_TRUE should only be passed if it is intended to verify total standard compliance of all fields in the input data.
Win32, x64, Linux.
Returns current date in yyyyMMdd formatted zero terminated string.
L_CHAR* GetCurrentDate();
// Returns zero when s1 and s2 are equal for given length
// Otherwise, returns non-zero
L_INT StrCmp(const L_CHAR *s1, const L_CHAR *s2, L_UINT length)
{
L_UINT index;
for (index = 0; index < length; index++)
{
if (s1[index] != s2[index])
return s1[index] - s2[index];
}
return 0;
}
L_INT BarCodeParseAAMVADataExample(pBARCODEDATA pBarCodeData)
{
if (!pBarCodeData)
return FAILURE;
AAMVAID id;
// Parse Barcode AAMVA data
L_INT nRet = L_BarCodeParseAAMVAData(pBarCodeData->pszBarCodeData, pBarCodeData->nSizeofBarCodeData, &id, L_FALSE);
if(nRet == SUCCESS)
{
// ID fields have been filled:
// .IssuerIdentificationNumber, a 6 digit identification number for an issuing authority. Typically a state DMV.
// .Jurisdiction, the L_AAMVA_JURISDICTION that issued this ID. Human readable (state or region).
// .Version, the version of the AAMVA CDS to which the data complies
// .JurisdictionVersion, a 2 character jurisdiction-specific version number of the encoded input data. Used by issuing authority only.
// .NumberOfEntries, the number of subfiles encoded in the input data. ie. the length of the .Subfiles field
// .Subfiles, an array of AAMVASUBFILEs with length == id.NumberOfEntries
// We can use convenience functions to parse all subfiles for common data elements:
// NOTE:
// Some issuing authorities encode full names in a single data element ("DAA" tag).
// eg. LAST,FIRST,MIDDLE
// FIRST MIDDLE LAST
// LAST@FIRST@MIDDLEINITIAL
//
// Others use newer tags to encode first, middle and last names separately.
// The convenience functions below check for the preferable newer tags first, then
// attempt to parse out the requested name from the "DAA" tag if necessary.
// Since issuing authorities use different encodings for the
// "DAA" element, the name convenience functions below are not guaranteed
// to work in all cases. If a name has been inferred from the "DAA" data element,
// bInferredFromFullName will be L_TRUE. In such cases, it may be best to manually
// read the "DAA" tag as well.
//First Name
L_BOOL bInferredFromFullName;
L_CHAR* pszFirstName = NULL;
nRet = L_BarCodeAAMVAIDFirstName(&id, &pszFirstName, &bInferredFromFullName);
if (nRet != SUCCESS)
return nRet;
//print pszFirstName, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszFirstName);
L_CHAR* pszMiddleName = NULL;
nRet = L_BarCodeAAMVAIDMiddleName(&id, &pszMiddleName);
if (nRet != SUCCESS)
return nRet;
//print pszMiddleName, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszMiddleName);
//Last Name
L_CHAR* pszLastName = NULL;
nRet = L_BarCodeAAMVAIDLastName(&id, &pszLastName, &bInferredFromFullName);
if (nRet != SUCCESS)
return nRet;
//print pszLastName, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszLastName);
//Street Address 1
L_CHAR * pszStreet1 = NULL;
nRet = L_BarCodeAAMVAIDAddressStreet1(&id, &pszStreet1);
if (nRet != SUCCESS)
return nRet;
//print pszStreet1, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszStreet1);
//Street Address 2
L_CHAR * pszStreet2 = NULL;
nRet = L_BarCodeAAMVAIDAddressStreet2(&id, &pszStreet2);
if (nRet != SUCCESS)
return nRet;
//print pszStreet2, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszStreet2);
//Address State Abbreviation (eg. "NC" for North Carolina)
L_CHAR * pszStateAbbr = NULL;
nRet = L_BarCodeAAMVAIDAddressStateAbbreviation(&id, &pszStateAbbr);
if (nRet != SUCCESS)
return nRet;
//print pszStateAbbr, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszStateAbbr);
//Address City
L_CHAR * pszCity = NULL;
nRet = L_BarCodeAAMVAIDAddressCity(&id, &pszCity);
if (nRet != SUCCESS)
return nRet;
//print pszCity, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszCity);
//Address Postal Code (eg. "282053571" for 28205-3571, separator(s) always omitted)
L_CHAR * pszZip = NULL;
nRet = L_BarCodeAAMVAIDAddressPostalCode(&id, &pszZip);
if (nRet != SUCCESS)
return nRet;
//print pszZip, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszZip);
//AAMVA Region
L_AAMVA_REGION rgn;
nRet = L_BarCodeAAMVAIDAddressRegion(&id, &rgn);
if (nRet != SUCCESS)
return nRet;
//Date of birth (in yyyMMdd format, eg. "19910824" for August 24th, 1991).
L_CHAR * pszDob = NULL;
nRet = L_BarCodeAAMVAIDDateOfBirth(&id, &pszDob);
if (nRet != SUCCESS)
return nRet;
//print pszDob, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszDob);
//Check if we can determine if ID holder is Over 18
L_BOOL bRes;
L_CHAR* currentDate = GetCurrentDate();
nRet = L_BarCodeAAMVAIDOver18Available(&id, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes)
{
//Over18 is available. Let's check if ID hold is 18+
nRet = L_BarCodeAAMVAIDOver18(&id, currentDate, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes){
//ID holder is over 18
//...
}
}
//Check if we can determine if ID holder is Over 19 (Canada)
nRet = L_BarCodeAAMVAIDOver19Available(&id, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes)
{
//Over19 is available. Let's check if ID hold is 19+
nRet = L_BarCodeAAMVAIDOver19(&id, currentDate, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes){
//ID holder is over 19
//...
}
}
//Check if we can determine if ID holder is Over 21
nRet = L_BarCodeAAMVAIDOver21Available(&id, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes)
{
//Over21 is available. Let's check if ID hold is 21+
nRet = L_BarCodeAAMVAIDOver21(&id, currentDate, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes){
//ID holder is over 21
//...
}
}
//Check if expiration is available
nRet = L_BarCodeAAMVAIDExpirationAvailable(&id, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes)
{
//Expiration is available
nRet = L_BarCodeAAMVAIDExpired(&id, currentDate, &bRes);
if (nRet != SUCCESS)
return nRet;
if(bRes){
//Card is expired
//...
}
}
//Expiration Date
L_CHAR * pszExpDate = NULL;
nRet = L_BarCodeAAMVAIDExpirationDate(&id, &pszExpDate);
if (nRet != SUCCESS)
return nRet;
//print pszExpDate, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszExpDate);
//Issue Date
L_CHAR * pszIssDate = NULL;
nRet = L_BarCodeAAMVAIDIssueDate(&id, &pszIssDate);
if (nRet != SUCCESS)
return nRet;
//print pszIssDate, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszIssDate);
//ID Number
L_CHAR * pszNum = NULL;
nRet = L_BarCodeAAMVAIDNumber(&id, &pszNum);
if (nRet != SUCCESS)
return nRet;
//print pszNum, display on UI, etc.
// ...
//Must free result
L_BarCodeAAMVAMemoryFree(pszNum);
//Eye Color
L_AAMVA_EYE_COLOR eye;
nRet = L_BarCodeAAMVAIDEyeColor(&id, &eye);
if (nRet != SUCCESS)
return nRet;
//print eye, display on UI, etc.
// ...
//Hair Color
L_AAMVA_HAIR_COLOR hair;
nRet = L_BarCodeAAMVAIDHairColor(&id, &hair);
if (nRet != SUCCESS)
return nRet;
//print hair, display on UI, etc.
// ...
//Sex
L_AAMVA_SEX sex;
nRet = L_BarCodeAAMVAIDSex(&id, &sex);
if (nRet != SUCCESS)
return nRet;
//print sex, display on UI, etc.
// ...
// We've exhausted all of our convenience functions. If we want to read data elements directly,
// we can loop over all data elements in all subfiles like so
// Use L_BarCodeGetAAMVADataElementInfo to get an array
// of information about every data element for a given
// AAMVA CDS version
AAMVADATAELEMENTINFO* dataElementInfoArray = NULL;
nRet = L_BarCodeGetAAMVADataElementInfo(id.Version, &dataElementInfoArray);
if (nRet != SUCCESS)
return nRet;
L_UINT i, j;
for (i = 0; i < (&id)->NumberOfEntries; i++)
{
AAMVASUBFILE subfile = (&id)->Subfiles[i];
for (j = 0; j < AAMVA_DEFINED_DATA_ELEMENTS_COUNT + subfile.JurisdictionSpecificDataElementCount; j++)
{
AAMVADATAELEMENT dataElement = subfile.DataElements[j];
if (&dataElement != NULL)
{
// Compare dataElement.ElementID to the tag being searched for, eg. "DDK"
// See AAMVA CDS for all potential entries
if (StrCmp(dataElement.ElementID, "DDK", 3) == 0)
{
//Get some information about the data element
AAMVADATAELEMENTINFO info = dataElementInfoArray[j];
//info.ElementID --> "DDK"
//info.FriendlyName --> "Organ Donor Indicator"
//info.Definition --> "Field that indicates that the cardholder is an organ donor = 1."
//info.LengthType --> L_AAMVA_LENGTH_TYPE_FIXED (alternative is variable)
//info.ValidMaxLength --> 1
//info.ValidCharacters --> L_AAMVA_VALID_CHARACTERS_NUMERIC (can be bitwise OR'd with other L_AAMVA_VALID_CHARACTER types -- alphabetical, special)
//info.ValidSubfileTypes --> L_AAMVA_SUBFILE_TYPE_DL | L_AAMVA_SUBFILE_TYPE_ID;
if(StrCmp(dataElement.Value, "1", 1) == 0)
{
//Cardholder is an organ doner
//...
}
}
}
}
}
//Must free data element info array
L_BarCodeAAMVAMemoryFree(dataElementInfoArray);
//Free the AAMVAID
nRet = L_BarCodeFreeAAMVAID(&id);
return nRet;
}
}