/*===========================================================================
FILE:
   GobiCMDLL.cpp

DESCRIPTION:
   Simple class to load and interface to the Gobi CM DLL
   
PUBLIC CLASSES AND METHODS:
   cGobiCMDLL
      This class loads the Gobi CM DLL and then interfaces to it

Copyright (c) 2013, The Linux Foundation. All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
    * Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
    * Neither the name of The Linux Foundation nor the names of its
      contributors may be used to endorse or promote products derived from 
      this software without specific prior written permission.


THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
===========================================================================*/

//---------------------------------------------------------------------------
// Include Files
//---------------------------------------------------------------------------
#include "GobiCMDLL.h"
#include "GobiConnectionMgmtAPIStructs.h"
#include "Gobi3000Translation.h"
#include <string.h>

/*=========================================================================*/
// cGobiCMDLL Methods
/*=========================================================================*/

/*===========================================================================
METHOD:
   GetString (Internal Method)

DESCRIPTION:
   Call a Gobi CM API function that returns a string

PARAMETERS:
   mpFnString  [ I ] - Gobi CM API function pointer
   tlvID       [ I ] - ID of response TLV that contains the string
   strSz       [ I ] - Max string size (including NULL terminator)
   pStr        [ O ] - Buffer to hold the string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetString(
   tFNGobiInputOutput         mpFnString,
   BYTE                       tlvID,
   BYTE                       strSz,
   CHAR *                     pStr )
{
   // Assume failure
   if (strSz > 0 && pStr != 0)
   {
      pStr[0] = 0;
   }

   // Query for string?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mpFnString == 0 || mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = mpFnString( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   std::map <UINT8, const sQMIRawContentHeader *> tlvs = GetTLVs( &rsp[0], lo );
   std::map <UINT8, const sQMIRawContentHeader *>::const_iterator pIter = tlvs.find( tlvID );
   if (pIter == tlvs.end())
   {
      return eGOBI_ERR_GENERAL;
   }

   const sQMIRawContentHeader * pTmp = pIter->second;
   ULONG strLen = (ULONG)pTmp->mLength;
   pTmp++;

   if (strLen != 0 && strSz > 0 && pStr != 0)
   {
      ULONG needLen = strLen;
      if (needLen + 1 > strSz)
      {
         needLen = strSz - 1;
      }

      memcpy( pStr, pTmp, needLen );
      pStr[needLen] = 0;
   }

   return status;
}

/*===========================================================================
METHOD:
   Connect (Public Method)

DESCRIPTION:
   Calls GobiConnect

PARAMETERS:
   pInterface  [ I ] - Interace to connect to

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::Connect( LPCSTR pInterface )
{
   // Connect to WDS, DMS, and NAS services
   ULONG svc[3] = { 1, 2, 3 };
   ULONG svcCount = 3;
   GOBIHANDLE handle = 0;
   ULONG status = GobiConnect( pInterface, &svcCount, &svc[0], &handle );
   if (status == 0)
   {
      if (svcCount == 3)
      {
         mhGobi = handle;
      }
      else
      {
         // We require WDS, DMS, and NAS services
         Disconnect();
         status = eGOBI_ERR_GENERAL;
      }
   }

   return status;
}

/*===========================================================================
METHOD:
   Disconnect (Public Method)

DESCRIPTION:
   Calls GobiDisconnect

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::Disconnect()
{
   if (mhGobi == 0)
   {
      return eGOBI_ERR_GENERAL;
   }

   return GobiDisconnect( mhGobi );
}

/*===========================================================================
ETHOD:
   StartDataSession (Public Method)

DESCRIPTION:
   Calls WDSStartNetworkInterface

PARAMETERS:
   pAPN           [ I ] - Access point name
   pUser          [ I ] - Username
   pPwd           [ I ] - Password
   pSessionID     [ O ] - Session ID
   pFailureCode   [ O ] - Failure code (if present)

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::StartDataSession(
   LPCSTR                     pAPN,
   LPCSTR                     pUser,
   LPCSTR                     pPwd,
   ULONG *                    pSessionID,
   ULONG *                    pFailureCode )
{
   // Assume failure
   if (pSessionID != 0)
   {
      *pSessionID = 0xFFFFFFFF;
   }

   // Start a data session?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   UINT8 req[1024] = { 0 };
   UINT8 * pData = (UINT8 *)&req[0];

   sQMIRawContentHeader * pTLV = (sQMIRawContentHeader *)pData;
   pTLV->mTypeID = 0x16;
   pTLV->mLength 
      = (UINT16)sizeof( sWDSStartNetworkInterfaceRequest_Authentication );     
   pData += sizeof( sQMIRawContentHeader );

   sWDSStartNetworkInterfaceRequest_Authentication * pAuth =
      (sWDSStartNetworkInterfaceRequest_Authentication *)pData;
   pAuth->mEnablePAP = 1;
   pAuth->mEnableCHAP = 1;
   pData += sizeof( sWDSStartNetworkInterfaceRequest_Authentication );

   if (pAPN != 0 && pAPN[0] != 0)
   {
      size_t len = strnlen( pAPN, 256 );

      pTLV = (sQMIRawContentHeader *)pData;
      pTLV->mTypeID = 0x14;
      pTLV->mLength = (UINT16)len;
      pData += sizeof( sQMIRawContentHeader );

      memcpy( pData, pAPN, len );
      pData += len;
   }

   if (pUser != 0 && pUser[0] != 0)
   {
      size_t len = strnlen( pUser, 256 );

      pTLV = (sQMIRawContentHeader *)pData;
      pTLV->mTypeID = 0x17;
      pTLV->mLength = (UINT16)len;
      pData += sizeof( sQMIRawContentHeader );

      memcpy( pData, pUser, len );
      pData += len;
   }

   if (pPwd != 0 && pPwd[0] != 0)
   {
      size_t len = strnlen( pPwd, 256 );

      pTLV = (sQMIRawContentHeader *)pData;
      pTLV->mTypeID = 0x18;
      pTLV->mLength = (UINT16)len;
      pData += sizeof( sQMIRawContentHeader );

      memcpy( pData, pPwd, len );
      pData += len;
   }
   
   ULONG li = (ULONG)pData - (ULONG)&req[0];
   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = WDSStartNetworkInterface( mhGobi, 300000, li, &req[0], &lo, &rsp[0] );

   // On success pSessionID is valid, on failure pFailureCode is valid
   ULONG status2 = ParseStartDataSession( lo, &rsp[0], pSessionID, pFailureCode );

   if (status == eGOBI_ERR_NONE)
   {
      return status2;
   }

   return status;
}

/*===========================================================================
METHOD:
   CancelDataSession (Public Method)

DESCRIPTION:
   Calls GobiCancel/WDSAbort

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::CancelDataSession()
{
   // Cancel outstanding API request?
   if (mhGobi == 0)
   {
      return eGOBI_ERR_GENERAL;
   }

   // Cancel the request with the API
   ULONG svcID = 1;
   ULONG txID = 0xFFFFFFFF;
   ULONG status = GobiCancel( mhGobi, svcID, &txID );
   if (status != 0 || txID == 0xFFFFFFFF)
   {
      return eGOBI_ERR_GENERAL;
   }

   UINT8 req[256] = { 0 };
   UINT8 * pData = (UINT8 *)&req[0];

   sQMIRawContentHeader * pTLV = (sQMIRawContentHeader *)pData;
   pTLV->mTypeID = 0x01;
   pTLV->mLength = (UINT16)sizeof( sAbortRequest_TransactionID );     
   pData += sizeof( sQMIRawContentHeader );

   sAbortRequest_TransactionID * pID =
      (sAbortRequest_TransactionID *)pData;
   pID->mTransactionID = (UINT16)txID;
   pData += sizeof( sAbortRequest_TransactionID );

   // Cancel the request with the device
   ULONG li = (ULONG)pData - (ULONG)&req[0];
   status = WDSAbort( mhGobi, 2000, li, &req[0], 0, 0 );
   return status;
}

/*===========================================================================
METHOD:
   StopDataSession (Public Method)

DESCRIPTION:
   Calls WDSStopNetworkInterface

PARAMETERS:
   sessionID   [ I ] - Session ID

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::StopDataSession( ULONG sessionID )
{
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   UINT8 req[256] = { 0 };
   ULONG li = 256;

   status = PackStopDataSession( &li, &req[0], sessionID );
   if (status != 0)
   {
      return status;
   }

   // Stop data session
   status = WDSStopNetworkInterface( mhGobi, 2000, li, &req[0], 0, 0 );
   return status;
}

/*===========================================================================
METHOD:
   GetSessionState (Public Method)

DESCRIPTION:
   Calls WDSGetPacketServiceStatus

PARAMETERS:
   pSessionState  [ O ] - Current session state

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetSessionState( ULONG * pSessionState )
{
   // Assume failure
   if (pSessionState != 0)
   {
      *pSessionState = 0xFFFFFFFF;
   }

   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = WDSGetPacketServiceStatus( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetSessionState( lo, &rsp[0], pSessionState );
   return status;
}

/*===========================================================================
METHOD:
   GetSessionDuration (Public Method)

DESCRIPTION:
   Calls WDSGetDataSessionDuration

PARAMETERS:
   pSessionDuration  [ O ] - Session duration

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetSessionDuration( ULONGLONG * pSessionDuration )
{
   // Assume failure
   if (pSessionDuration != 0)
   {
      *pSessionDuration = 0xFFFFFFFF;
   }

   // Query for session duration
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = WDSGetDataSessionDuration( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetSessionDuration( lo, &rsp[0], pSessionDuration );
   return status;
}

/*===========================================================================
METHOD:
   GetDataBearerTechnology (Public Method)

DESCRIPTION:
   Calls WDSGetDataBearerTechnology

PARAMETERS:
   pDataBearerTech   [ O ] - Data bearer technology

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetDataBearerTechnology( ULONG * pDataBearerTech )
{
   // Assume failure
   if (pDataBearerTech != 0)
   {
      *pDataBearerTech = 0xFFFFFFFF;
   }

   // Query for data bearer duration?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = WDSGetDataBearerTechnology( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetDataBearerTechnology( lo, &rsp[0], pDataBearerTech );
   return status;
}

/*===========================================================================
METHOD:
   GetConnectionRate (Public Method)

DESCRIPTION:
   Calls WDSGetChannelRates

PARAMETERS:
   pCurTX      [ O ] - Current TX rate
   pCurRX      [ O ] - Current RX rate
   pMaxTX      [ O ] - Max TX rate
   pMaxRX      [ O ] - Max RX rate

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetConnectionRate(
   ULONG *                     pCurTX,
   ULONG *                     pCurRX,
   ULONG *                     pMaxTX,
   ULONG *                     pMaxRX )
{
   // Assume failure
   pCurTX != 0 ? *pCurTX = 0xFFFFFFFF : 0;
   pCurRX != 0 ? *pCurRX = 0xFFFFFFFF : 0;
   pMaxTX != 0 ? *pMaxTX = 0xFFFFFFFF : 0;
   pMaxRX != 0 ? *pMaxRX = 0xFFFFFFFF : 0;

   // Query for rates?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = WDSGetChannelRates( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetConnectionRate( lo, 
                                    &rsp[0], 
                                    pCurTX, 
                                    pCurRX, 
                                    pMaxTX, 
                                    pMaxRX );
   return status;
}

/*===========================================================================
METHOD:
   GetFirmwareRevision (Public Method)

DESCRIPTION:
   Calls DMSGetDeviceRevision

PARAMETERS:
   strSz      [ I ] - Maximum number of characters
   pStr       [ O ] - Firmware revision string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetFirmwareRevision( 
   BYTE                       strSz,
   CHAR *                     pStr )
{
   return GetString( DMSGetDeviceRevision, 0x01, strSz, pStr );
}

/*===========================================================================
METHOD:
   GetManufacturer (Public Method)

DESCRIPTION:
   Calls DMSGetDeviceManfacturer

PARAMETERS:
   strSz       [ I ] - Maximum string size
   pStr        [ O ] - Manufacturer string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetManufacturer(
   BYTE                       strSz,
   CHAR *                     pStr )
{
   return GetString( DMSGetDeviceManfacturer, 0x01, strSz, pStr );
}

/*===========================================================================
METHOD:
   GetModelID (Public Method)

DESCRIPTION:
   Calls GetModelID

PARAMETERS:
   strSz       [ I ] - Max string size
   pStr        [ O ] - Model ID string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetModelID(
   BYTE                       strSz,
   CHAR *                     pStr )
{
   return GetString( DMSGetDeviceModel, 0x01, strSz, pStr );
}

/*===========================================================================
METHOD:
   GetHardwareRevision (Public Method)

DESCRIPTION:
   Calls DMSGetHardwareRevision

PARAMETERS:
   strSz       [ I ] - Max size of string
   pStr        [ O ] - Hardware revision string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetHardwareRevision(
   BYTE                       strSz,
   CHAR *                     pStr )
{
   return GetString( DMSGetHardwareRevision, 0x01, strSz, pStr );
}

/*===========================================================================
METHOD:
   GetVoiceNumber (Public Method)

DESCRIPTION:
   Calls GetVoiceNumber

PARAMETERS:
   voiceSz     [ I ] - Max characters in voice string
   pVoiceStr   [ O ] - Voice number string
   minSz       [ I ] - Max characters in MIN string
   pMINStr     [ O ] - MIN string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetVoiceNumber(
   BYTE                       voiceSz,
   CHAR *                     pVoiceStr,
   BYTE                       minSz,
   CHAR *                     pMINStr )
{
   // Assume failure
   if (voiceSz > 0 && pVoiceStr != 0)
   {
      pVoiceStr[0] = 0;
   }

   if (minSz > 0 && pMINStr != 0)
   {
      pMINStr[0] = 0;
   }

   // Query for voice numbers?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = DMSGetDeviceVoiceNumber( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetVoiceNumber( lo, 
                                 &rsp[0], 
                                 voiceSz, 
                                 pVoiceStr, 
                                 minSz, 
                                 pMINStr );
   return status;
}

/*===========================================================================
METHOD:
   GetSerialNumbers (Public Method)

DESCRIPTION:
   Calls DMSGetDeviceSerialNumbers

PARAMETERS:
   esnSz       [ I ] - ESN size
   pESNStr     [ O ] - ESN string
   imeiSz      [ I ] - IMEI size
   pIMEIStr    [ O ] - IMSI string
   meidSz      [ I ] - MEID size
   pMEIDStr    [ O ] - MEID string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetSerialNumbers(
   BYTE                       esnSz,
   CHAR *                     pESNStr,
   BYTE                       imeiSz,
   CHAR *                     pIMEIStr,
   BYTE                       meidSz,
   CHAR *                     pMEIDStr )
{
   // Assume failure
   if (esnSz > 0 && pESNStr != 0)
   {
      pESNStr[0] = 0;
   }

   if (imeiSz > 0 && pIMEIStr != 0)
   {
      pIMEIStr[0] = 0;
   }

   if (meidSz > 0 && pMEIDStr != 0)
   {
      pMEIDStr[0] = 0;
   }

   // Query for serial numbers?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = DMSGetDeviceSerialNumbers( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetSerialNumbers( lo, 
                                   &rsp[0], 
                                   esnSz, 
                                   pESNStr,
                                   imeiSz,
                                   pIMEIStr,
                                   meidSz,
                                   pMEIDStr );
   return status;
}

/*===========================================================================
METHOD:
   GetIMSI (Public Method)

DESCRIPTION:
   Get IMSI

PARAMETERS:
   imsiSz      [ I ] - IMSI size
   pIMSIStr    [ O ] - IMSI string

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetIMSI(
   BYTE                       imsiSz,
   CHAR *                     pIMSIStr )
{
   return GetString( DMSGetDeviceVoiceNumber, 0x11, imsiSz, pIMSIStr );
}

/*===========================================================================
METHOD:
   GetSignalStrengths (Public Method)

DESCRIPTION:
   Calls NASGetSignalStrength

PARAMETERS:
   pSigStrengths     [ O ] - Received signal strength
   pRadioInterfaces  [ O ] - Radio interface technology

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetSignalStrengths( 
   INT8 *                     pSigStrengths,
   ULONG *                    pRadioInterfaces )
{
   // Assume failure
   for (ULONG s = 0; s < MAX_SIGNALS; s++)
   {
      pSigStrengths[s] = 0;
      pRadioInterfaces[s] = 0xFFFFFFFF;
   }

   // Query for signal strengths?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 1024;
   BYTE rsp[1024] = { 0 };
   status = NASGetSignalStrength( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }
   
   status = ParseGetSignalStrength( lo, 
                                    &rsp[0], 
                                    pSigStrengths,
                                    pRadioInterfaces );
   return status;
}

/*===========================================================================
METHOD:
   GetServingNetwork (Public Method)

DESCRIPTION:
   Calls NASGetServingSystem

PARAMETERS:
   pDataCapabilities [ O ] - Data capabilities
   pMCC              [ O ] - Mobile country code
   pMNC              [ O ] - Mobile network code
   nameSize          [ I ] - Network name max size
   pName             [ O ] - Network name
   pSID              [ O ] - System ID
   pNID              [ O ] - Network ID
   pRoam             [ O ] - Roaming indicator
   
RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetServingNetwork(
   ULONG *                    pDataCapabilities,
   WORD *                     pMCC,
   WORD *                     pMNC,
   BYTE                       nameSize,
   CHAR *                     pName,
   WORD *                     pSID,
   WORD *                     pNID,
   ULONG *                    pRoam )
{
   // Assume failure
   for (ULONG d = 0; d < MAX_DATA_CAPABILITIES; d++)
   {
      pDataCapabilities[d] = 0xFFFFFFFF;
   }

   if (nameSize > 0 && pName != 0)
   {
      pName[0] = 0;
   }

   pMCC != 0 ? *pMCC = 0xFFFF : 0;
   pMNC != 0 ? *pMNC = 0xFFFF : 0;
   pRoam != 0 ? *pRoam = 0xFFFFFFFF : 0;
   pSID != 0 ? *pSID = 0xFFFF : 0;
   pNID != 0 ? *pNID = 0xFFFF : 0;

   // Query for serving system?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 8096;
   BYTE rsp[8096] = { 0 };
   status = NASGetServingSystem( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   std::map <UINT8, const sQMIRawContentHeader *> tlvs = GetTLVs( &rsp[0], lo );
   std::map <UINT8, const sQMIRawContentHeader *>::const_iterator pIter = tlvs.find( 0x11 );
   if (pIter != tlvs.end())
   {
      const sQMIRawContentHeader * pTmp = pIter->second;
      ULONG tlvLen = (ULONG)pTmp->mLength;
      ULONG dsLen = (ULONG)sizeof( sNASGetServingSystemResponse_DataServices ); 
      if (tlvLen < dsLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pTmp++;
      const sNASGetServingSystemResponse_DataServices * pDS =
         (const sNASGetServingSystemResponse_DataServices *)pTmp;

      ULONG dcCount = (ULONG)pDS->mNumberOfDataCapabilities;
      ULONG dcSz = (ULONG)sizeof( eQMINASDataServiceCapabilities2 );
      dsLen += dcCount * dcSz;
      if (tlvLen < dsLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pDS++;
      eQMINASDataServiceCapabilities2 * pCap = (eQMINASDataServiceCapabilities2 *)pDS;
      if (dcCount > MAX_DATA_CAPABILITIES)
      {
         dcCount = MAX_DATA_CAPABILITIES;
      }

      for (ULONG i = 0; i < dcCount; i++)
      {
        pDataCapabilities[i] = (ULONG)*pCap++;
      }
   }

   pIter = tlvs.find( 0x12 );
   if (pIter != tlvs.end())
   {
      const sQMIRawContentHeader * pTmp = pIter->second;
      ULONG tlvLen = (ULONG)pTmp->mLength;
      ULONG plmnLen = (ULONG)sizeof( sNASGetServingSystemResponse_CurrentPLMN ); 
      if (tlvLen < plmnLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pTmp++;
      const sNASGetServingSystemResponse_CurrentPLMN * pPLMN =
         (const sNASGetServingSystemResponse_CurrentPLMN *)pTmp;

      ULONG strLen = (ULONG)pPLMN->mDescriptionLength;
      plmnLen += strLen;
      if (tlvLen < plmnLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pMCC != 0 ? *pMCC = (ULONG)pPLMN->mMobileCountryCode : 0;
      pMNC != 0 ? *pMNC = (ULONG)pPLMN->mMobileNetworkCode : 0;
      pPLMN++;

      if (strLen != 0 && nameSize > 0 && pName != 0)
      {
         ULONG needLen = strLen;
         if (needLen + 1 > nameSize)
         {
            needLen = nameSize - 1;
         }

         memcpy( pName, pPLMN, needLen );
         pName[needLen] = 0;
      }
   }

   pIter = tlvs.find( 0x13 );
   if (pIter != tlvs.end())
   {
      const sQMIRawContentHeader * pTmp = pIter->second;
      ULONG tlvLen = (ULONG)pTmp->mLength;
      ULONG sysLen = (ULONG)sizeof( sNASGetServingSystemResponse_SystemID ); 
      if (tlvLen < sysLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pTmp++;
      const sNASGetServingSystemResponse_SystemID * pSys =
         (const sNASGetServingSystemResponse_SystemID *)pTmp;

      pSID != 0 ? *pSID = (ULONG)pSys->mSystemID : 0;
      pNID != 0 ? *pNID = (ULONG)pSys->mNetworkID : 0;
   }

   pIter = tlvs.find( 0x16 );
   if (pIter != tlvs.end())
   {
      const sQMIRawContentHeader * pTmp = pIter->second;
      ULONG tlvLen = (ULONG)pTmp->mLength;
      ULONG roamLen = (ULONG)sizeof( sNASGetServingSystemResponse_DefaultRoaming ); 
      if (tlvLen < roamLen)
      {
         return eGOBI_ERR_GENERAL;
      }

      pTmp++;
      const sNASGetServingSystemResponse_DefaultRoaming * pDR =
         (const sNASGetServingSystemResponse_DefaultRoaming *)pTmp;

      pRoam != 0 ? *pRoam = (ULONG)pDR->mRoamingIndicator : 0;
   }

   return status;
}

/*===========================================================================
METHOD:
   GetHomeNetwork (Public Method)

DESCRIPTION:
   Calls NASGetHomeNetwork

PARAMETERS:
   pHomeMCC       [ O ] - Mobile country code
   pHomeMNC       [ O ] - Mobile network code
   homeNameSize   [ I ] - Max name size
   pHomeName      [ O ] - Home network name
   pSID           [ O ] - System ID
   pNID           [ O ] - Network ID

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::GetHomeNetwork(
   WORD *                     pHomeMCC,
   WORD *                     pHomeMNC,
   BYTE                       homeNameSize,
   CHAR *                     pHomeName,
   WORD *                     pSID,
   WORD *                     pNID )
{
   // Assume failure
   if (homeNameSize > 0 && pHomeName != 0)
   {
      pHomeName[0] = 0;
   }

   pHomeMCC != 0 ? *pHomeMCC = 0xFFFF : 0;
   pHomeMNC != 0 ? *pHomeMNC = 0xFFFF : 0;
   pSID != 0 ? *pSID = 0xFFFF : 0;
   pNID != 0 ? *pNID = 0xFFFF : 0;

   // Query for home system?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   ULONG lo = 8096;
   BYTE rsp[8096] = { 0 };
   status = NASGetHomeNetwork( mhGobi, 2000, 0, 0, &lo, &rsp[0] );
   if (status != 0)
   {
      return status;
   }

   status = ParseGetHomeNetwork( lo, 
                                 &rsp[0], 
                                 pHomeMCC,
                                 pHomeMNC,
                                 homeNameSize,
                                 pHomeName,
                                 pSID,
                                 pNID );
   return status;
}

/*===========================================================================
METHOD:
   SetWDSEventReportCB (Public Method)

DESCRIPTION:
   Calls WDSSetEventReport/SetGenericCallback

PARAMETERS:
   pCallback   [ I ] - Callback function pointer
   interval    [ I ] - Interval (in seconds) for transfer statistics

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::SetWDSEventReportCB( 
   tFNGenericCallback         pCallback,
   BYTE                       interval )
{
   // Set WDS event callback?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   // Configure the QMI service
   UINT8 req[1024] = { 0 };
   UINT8 * pData = (UINT8 *)&req[0];

   sQMIRawContentHeader * pTLV = (sQMIRawContentHeader *)pData;
   pTLV->mTypeID = 0x11;
   pTLV->mLength = (UINT16)sizeof( sWDSSetEventReportRequest_TransferStatisticsIndicator );     
   pData += sizeof( sQMIRawContentHeader );

   sWDSSetEventReportRequest_TransferStatisticsIndicator * pTS =
      (sWDSSetEventReportRequest_TransferStatisticsIndicator *)pData;
   pTS->mTransferStatisticsIntervalSeconds = interval;
   pTS->mReportTXPacketSuccesses = 0;
   pTS->mReportRXPacketSuccesses = 0;
   pTS->mReportTXPacketErrors = 0;
   pTS->mReportRXPacketErrors = 0;
   pTS->mReportTXOverflows = 0;
   pTS->mReportRXOverflows = 0;
   pTS->mTXByteTotal = 1;
   pTS->mRXByteTotal = 1;
   pData += sizeof( sWDSSetEventReportRequest_TransferStatisticsIndicator );

   pTLV = (sQMIRawContentHeader *)pData;
   pTLV->mTypeID = 0x12;
   pTLV->mLength = (UINT16)sizeof( sWDSSetEventReportRequest_DataBearerTechnologyIndicator );     
   pData += sizeof( sQMIRawContentHeader );

   sWDSSetEventReportRequest_DataBearerTechnologyIndicator * pTI =
      (sWDSSetEventReportRequest_DataBearerTechnologyIndicator *)pData;
   pTI->mReportDataBearerTechnology = 1;
   pData += sizeof( sWDSSetEventReportRequest_DataBearerTechnologyIndicator );

   ULONG li = (ULONG)pData - (ULONG)&req[0];
   status = WDSSetEventReport( mhGobi, 2000, li, &req[0], 0, 0 );
   if (status != 0)
   {
      return status;
   }

   // Configure the callback with the API
   status = SetGenericCallback( mhGobi, 1, 1, pCallback );
   return status;
}

/*===========================================================================
METHOD:
   SetWDSSessionStateCB (Public Method)

DESCRIPTION:
   Calls SetGenericCallback

PARAMETERS:
   pCallback   [ I ] - Callback function pointer

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::SetWDSSessionStateCB( tFNGenericCallback pCallback )
{
   // Set WDS packet service status callback?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   // Configure the callback with the API
   status = SetGenericCallback( mhGobi, 1, 34, pCallback );
   return status;
}

/*===========================================================================
METHOD:
   SetNASEventReportCB (Public Method)

DESCRIPTION:
   Calls NASSetEventReport/SetGenericCallback

PARAMETERS:
   pCallback      [ I ] - Callback function pointer
   thresholdsSize [ I ] - Threshold size
   pThresholds    [ I ] - Array of thresholds

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::SetNASEventReportCB(
   tFNGenericCallback         pCallback,
   BYTE                       thresholdsSize,
   INT8 *                     pThresholds )
{
   // Set NAS event report callback request?
   ULONG status = eGOBI_ERR_GENERAL;
   if ( (mhGobi == 0)
   ||   (thresholdsSize > 0 && pThresholds == 0) )
   {
      return status;
   }

   // Configure the QMI service
   UINT8 req[1024] = { 0 };
   UINT8 * pData = (UINT8 *)&req[0];

   sQMIRawContentHeader * pTLV = (sQMIRawContentHeader *)pData;
   pTLV->mTypeID = 0x10;
   pTLV->mLength = (UINT16)sizeof( sNASSetEventReportRequest_SignalIndicator );     
   pTLV->mLength += (UINT16)thresholdsSize;
   pData += sizeof( sQMIRawContentHeader );

   sNASSetEventReportRequest_SignalIndicator * pSI =
      (sNASSetEventReportRequest_SignalIndicator *)pData;
   pSI->mReportSignalStrength = 1;
   pSI->mNumberOfThresholds = thresholdsSize;
   pData += sizeof( sNASSetEventReportRequest_SignalIndicator );

   for (UINT8 i = 0; i < thresholdsSize; i++)
   {
      INT8 * pThresh = (INT8 *)pData;
      *pThresh = pThresholds[i];
      pData++;
   }

   ULONG li = (ULONG)pData - (ULONG)&req[0];
   status = NASSetEventReport( mhGobi, 2000, li, &req[0], 0, 0 );
   if (status != 0)
   {
      return status;
   }

   // Configure the callback with the API
   status = SetGenericCallback( mhGobi, 3, 2, pCallback );
   return status;
}

/*===========================================================================
METHOD:
   SetNASServingSystemCB (Public Method)

DESCRIPTION:
   Calls SetGenericCallback

PARAMETERS:
   pCallback      [ I ] - Callback function pointer

RETURN VALUE:
   ULONG
===========================================================================*/
ULONG cGobiCMDLL::SetNASServingSystemCB( tFNGenericCallback pCallback )
{
   // Set NAS serving system request?
   ULONG status = eGOBI_ERR_GENERAL;
   if (mhGobi == 0)
   {
      return status;
   }

   // Configure the callback with the API
   status = SetGenericCallback( mhGobi, 3, 36, pCallback );
   return status;
}
