/*===========================================================================
FILE: 
   GobiQDLCore.cpp

DESCRIPTION:
   QUALCOMM Gobi QDL Based API Core

PUBLIC CLASSES AND FUNCTIONS:
   cGobiQDLCore

Copyright (c) 2011, Code Aurora Forum. 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 Code Aurora Forum 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 "StdAfx.h"
#include "GobiQDLCore.h"

#include "QDLBuffers.h"
#include "ProtocolNotification.h"

#include <glob.h>

//---------------------------------------------------------------------------
// Definitions
//---------------------------------------------------------------------------

// Default/minimum timeout for QCWWAN QMI requests
const ULONG DEFAULT_GOBI_QDL_TIMEOUT = 4000;
const ULONG MINIMUM_GOBI_QDL_TIMEOUT = 2000;

/*=========================================================================*/
// cGobiQDLCore Methods
/*=========================================================================*/

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

DESCRIPTION:
   Constructor
  
RETURN VALUE:
   None
===========================================================================*/
cGobiQDLCore::cGobiQDLCore()
   :  mQDL( 512, 512 ),
      mQDLPortNode( "" ),
      mQDLTimeout( DEFAULT_GOBI_QDL_TIMEOUT )
{
   // Nothing to do
}

/*===========================================================================
METHOD:
   ~cGobiQDLCore (Public Method)

DESCRIPTION:
   Destructor
  
RETURN VALUE:
   None
===========================================================================*/
cGobiQDLCore::~cGobiQDLCore()
{
   // Nothing to do
}

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

DESCRIPTION:
   Initialize the object
  
RETURN VALUE:
   bool
===========================================================================*/
bool cGobiQDLCore::Initialize()
{
   // Nothing to do
   return true;
}

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

DESCRIPTION:
   Cleanup the object
  
RETURN VALUE:
   bool
===========================================================================*/
bool cGobiQDLCore::Cleanup()
{
   // Just in case
   CloseQDLPort( false );

   return true;
}

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

DESCRIPTION:
   Return the set of available Gobi QDL ports

RETURN VALUE:
   std::vector <sDeviceID>
===========================================================================*/
std::vector <std::string> cGobiQDLCore::GetAvailableQDLPorts()
{
   std::vector <std::string> devices;

   std::string path = "/sys/bus/usb/devices/";
   char buf[PATH_MAX];

   glob_t files;
   int ret = glob( (path + "*/*/ttyUSB*").c_str(), 
                   0, 
                   NULL, 
                   &files );
   char *s;
   if (ret != 0)
   {
      // Glob failure
      return devices;
   }

   for (int i = 0; i < files.gl_pathc; i++)
   {
      // Example "/sys/bus/usb/devices/8-1/8-1:1.1/ttyUSB0"
      std::string nodePath = files.gl_pathv[i];
      
      int lastSlash = nodePath.find_last_of( "/" );

      // This is what we want to return if everything else matches
      std::string deviceNode = nodePath.substr( lastSlash + 1 );

      // Move down one directory to the interface level
      std::string curPath = nodePath.substr( 0, lastSlash );

      // Read bInterfaceNumber
      int handle = open( (curPath + "/bInterfaceNumber").c_str(), 
                         O_RDONLY );
      if (handle == -1)
      {
         continue;
      }

      char buff[4];
      memset( buff, 0, 4 );
      
      bool bFound = false;
      ret = read( handle, buff, 2 );
      if (ret == 2)
      {
         // Interface 1 or 0
         ret = strncmp( buff, "01", 2 );
         if (ret == 0)
         {
            bFound = true;
         }
         ret = strncmp( buff, "00", 2 );
         if (ret == 0)
         {
            bFound = true;
         }

      }
      close( handle );

      if (bFound == false)
      {
         continue;
      }

      memset(buf, 0, sizeof(buf));
      if (readlink((curPath + "/driver").c_str(), buf, sizeof(buf)) < 0)
         continue;
      buf[sizeof(buf) - 1] = '\0';
      s = strrchr(buf, '/');
      s = s ? s + 1 : buf;

      if (strcmp(s, "qcserial") && strcmp(s, "QCSerial2k")
          && strcmp(s, "GobiSerial"))
         continue;

      // Success!
      devices.push_back( deviceNode );
   }
   globfree( &files );

   return devices;
}

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

DESCRIPTION:
   Set the timeout for all subsequent QDL transactions

PARAMETERS:
   to          [ I ] - Timeout value (in milliseconds)
   
RETURN VALUE:
   eGobiError
===========================================================================*/
eGobiError cGobiQDLCore::SetQDLTimeout( ULONG to )
{
   if (to < MINIMUM_GOBI_QDL_TIMEOUT)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

    mQDLTimeout = to;
    return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   This function opens the specified QDL port of the device

PARAMETERS:
   portID         [ I ] - ID of QDL port to connect to 
   bBARMode       [ I ] - Request boot and recovery mode feature
   pMajorVersion  [ O ] - Major version of the device boot downloader
   pMinorVersion  [ O ] - Minor version of the device boot downloader

RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::OpenQDLPort( 
   std::string &              portID,
   ULONG                      bBARMode,
   ULONG *                    pMajorVersion, 
   ULONG *                    pMinorVersion )
{
   if (portID.empty() == true || pMajorVersion == 0 || pMinorVersion == 0)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   // First disconnect from current port (if any)
   CloseQDLPort( false );

   // Validate port ID
   std::string foundDevice;
   std::vector <std::string> availPorts = GetAvailableQDLPorts();
   for (int index = 0; index < availPorts.size(); index++)
   {
      if (availPorts[index] == portID)
      {
         foundDevice = availPorts[index];
         break;
      }
   }

   if (foundDevice.empty() == true)
   {
      return eGOBI_ERR_INVALID_DEVID;
   }

   // Initialize server (we don't care about the return code
   // since the following Connect() call will fail if we are
   // unable to initialize the server)
   mQDL.Initialize();

   // Connect to the port
   std::string deviceStr = "/dev/" + foundDevice;
   bool bOK = mQDL.Connect( deviceStr.c_str() );
   if (bOK == false)
   {
      return eGOBI_ERR_CONNECT;
   }

   // Store port ID (we are connected)
   mQDLPortNode = foundDevice;

   // Build the hello request
   bool bBARFeature = bBARMode != 0;
   sSharedBuffer * pHelloBuf = sQDLHello::BuildHelloReq( bBARFeature );
   if (pHelloBuf == 0)
   {
      return eGOBI_ERR_MEMORY;
   }

   // Send the hello request and wait for the response
   sProtocolBuffer rsp;
   rsp = SendQDL( pHelloBuf );
   if (rsp.IsValid() == false)
   {
      return GetCorrectedLastError();
   }

   // Extract major and minor boot downloader versions
   ULONG majVer;
   ULONG minVer;
   sQDLHello helloRsp( rsp.GetSharedBuffer() );
   if (helloRsp.GetBootVersionInfo( majVer, minVer ) == false)
   {
      sQDLError errRsp( rsp.GetSharedBuffer() );
      if (errRsp.IsValid() == true)
      {
         eQDLError qdlErr = errRsp.GetErrorCode();
         return GetCorrectedQDLError( qdlErr );
      }

      return eGOBI_ERR_MALFORMED_RSP;
   }

   // NOTE: in the current firmware implimentation, this cannot happen.
   // No hello response will be received in case of feature mismatch.
   if (bBARFeature == true)
   {
      const sQDLRawHelloRsp * pTmpRsp = helloRsp.GetResponse();
      if (pTmpRsp == 0)
      {
         return eGOBI_ERR_MALFORMED_RSP;
      }

      if ( (pTmpRsp->mFeatures & QDL_FEATURE_BAR_MODE) == 0)
      {
         return eGOBI_ERR_QDL_BAR_MODE;
      }
   }

   *pMajorVersion = majVer;
   *pMinorVersion = minVer;
   return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   This function closes the currently open QDL port of the device

PARAMETERS:
   bInformDevice  [ I ] - Inform device that QDL port is being closed? 

RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::CloseQDLPort( bool bInformDevice )
{
   // Assume success
   eGobiError rc = eGOBI_ERR_NONE;
   if (mQDLPortNode.empty() == true)
   {
      rc = eGOBI_ERR_NO_CONNECTION;
   }
   else if (bInformDevice == true)
   {
      BYTE cmd = (BYTE)eQDL_CMD_SESSION_CLOSE_REQ;
      eProtocolType pt = ePROTOCOL_QDL_TX;

      sSharedBuffer * pReq = 0;
      pReq = new sSharedBuffer( (const BYTE *)&cmd, 1, pt );
      if (pReq == 0)
      {
         rc = eGOBI_ERR_MEMORY;
      }
      else
      {
         sProtocolBuffer rsp = SendQDL( pReq, 0, 0, false );
         rc = GetLastError();
      }
   }

   mQDL.Disconnect();
   mQDL.Exit();

   mQDLPortNode.clear();

   return rc;
}

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

DESCRIPTION:
   This function gets the current images preference as reported by the 
   device boot downloader

PARAMETERS:
   pImageListSize [I/O] - Upon input the maximum number of elements that the 
                          image info list can contain.  Upon successful output 
                          the actual number of elements in the image info list
   pImageList     [ O ] - The image info list
  
RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::GetQDLImagesPreference( 
   ULONG *                    pImageListSize, 
   BYTE *                     pImageList )
{
   if (pImageListSize == 0 || *pImageListSize == 0 || pImageList == 0)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   BYTE cmd = (BYTE)eQDL_CMD_GET_IMAGE_PREF_REQ;
   eProtocolType pt = ePROTOCOL_QDL_TX;

   sSharedBuffer * pReq = 0;
   pReq = new sSharedBuffer( (const BYTE *)&cmd, 1, pt );
   if (pReq == 0)
   {
      return eGOBI_ERR_MEMORY;
   }

   ULONG maxImages = (ULONG)*pImageListSize;
   *pImageListSize = 0;

   sProtocolBuffer rsp = SendQDL( pReq );
   if (rsp.IsValid() == false)
   {
      return GetCorrectedLastError();
   }

   sQDLGetImagePref prefRsp( rsp.GetSharedBuffer() );
   if (prefRsp.IsValid() == false)
   {
      sQDLError errRsp( rsp.GetSharedBuffer() );
      if (errRsp.IsValid() == true)
      {
         eQDLError qdlErr = errRsp.GetErrorCode();
         return GetCorrectedQDLError( qdlErr );
      }

      return eGOBI_ERR_MALFORMED_RSP;
   }

   std::list <sQDLRawImageID> imageIDs = prefRsp.GetImageIDs();
   ULONG imageCount = (ULONG)imageIDs.size();
   if (imageCount > maxImages)
   {
      imageCount = maxImages;
   }

   sQDLRawImageID * pOutList = (sQDLRawImageID *)pImageList;
   std::list <sQDLRawImageID>::const_iterator pIter = imageIDs.begin();
   for (ULONG i = 0; i < imageCount; i++)
   {
      *pOutList++ = *pIter++;
   }

   *pImageListSize = imageCount;
   return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   This function prepares the device boot downloader for an image write

PARAMETERS:
   imageType   [ I ] - Type of image being written 
   imageSize   [ I ] - Size of image being written
   pBlockSize  [I/O] - Upon input the maximum size of image block supported 
                       by host, upon successful output the maximum size of 
                       image block supported by device

RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::PrepareQDLImageWrite( 
   BYTE                       imageType,
   ULONG                      imageSize,
   ULONG *                    pBlockSize )
{
   eQDLImageType it = (eQDLImageType)imageType;
   if (::IsValid( it ) == false)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   if (pBlockSize == 0 || *pBlockSize == 0 || *pBlockSize > QDL_MAX_CHUNK_SIZE)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   sSharedBuffer * pReq = 0;
   pReq = sQDLOpenUnframed::BuildOpenUnframedReq( it, imageSize, *pBlockSize );
   if (pReq == 0)
   {
      return eGOBI_ERR_MEMORY;
   }

   sProtocolBuffer rsp = SendQDL( pReq );
   if (rsp.IsValid() == false)
   {
      return GetCorrectedLastError();
   }

   ULONG tmp;
   sQDLOpenUnframed openRsp( rsp.GetSharedBuffer() );
   const sQDLRawOpenUnframedRsp * pTmp = openRsp.GetResponse();
   if (pTmp == 0 || openRsp.GetChunkSize( tmp ) == false)
   {
      sQDLError errRsp( rsp.GetSharedBuffer() );
      if (errRsp.IsValid() == true)
      {
         eQDLError qdlErr = errRsp.GetErrorCode();
         return GetCorrectedQDLError( qdlErr );
      }

      return eGOBI_ERR_MALFORMED_RSP;
   }

   if (openRsp.IsSuccess() == false)
   {
      switch ((eQDLOpenStatus)pTmp->mStatus)
      {
         case eQDL_OPEN_STATUS_SIZE:
            return eGOBI_ERR_QDL_OPEN_SIZE;

         case eQDL_OPEN_STATUS_BAD_TYPE:
            return eGOBI_ERR_QDL_OPEN_TYPE;

         case eQDL_OPEN_STATUS_PROTECTION:
            return eGOBI_ERR_QDL_OPEN_PROT;

         case eQDL_OPEN_STATUS_NOT_NEEDED:
            return eGOBI_ERR_QDL_OPEN_SKIP;
      }

      return eGOBI_ERR_QDL_ERR_GENERAL;
   }

   *pBlockSize = tmp;
   return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   This function writes the specified image block to the device

PARAMETERS:
   sequenceNumber [ I ] - Sequence number for image write 
   blockSize      [ I ] - Size of image block
   pImageBlock    [ I ] - Image block
  
RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::WriteQDLImageBlock( 
   USHORT                     sequenceNumber,
   ULONG                      blockSize,
   BYTE *                     pImageBlock )
{
   if (blockSize > QDL_MAX_CHUNK_SIZE || pImageBlock == 0)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   sSharedBuffer * pReq = 0;
   pReq = sQDLWriteUnframed::BuildWriteUnframedReq( sequenceNumber, 
                                                    blockSize );

   if (pReq == 0)
   {
      return eGOBI_ERR_MEMORY;
   }

   sProtocolBuffer rsp = SendQDL( pReq, pImageBlock, blockSize );
   if (rsp.IsValid() == false)
   {
      return GetCorrectedLastError();
   }

   sQDLWriteUnframed writeRsp( rsp.GetSharedBuffer() );
   const sQDLRawWriteUnframedRsp * pTmp = writeRsp.GetResponse();
   if (pTmp == 0)
   {
      sQDLError errRsp( rsp.GetSharedBuffer() );
      if (errRsp.IsValid() == true)
      {
         eQDLError qdlErr = errRsp.GetErrorCode();
         return GetCorrectedQDLError( qdlErr );
      }

      return eGOBI_ERR_MALFORMED_RSP;
   }

   if (writeRsp.IsSuccess() == false)
   {
      switch ((eQDLWriteStatus)pTmp->mStatus)
      {
         case eQDL_WRITE_STATUS_CRC:
            return eGOBI_ERR_QDL_CRC;

         case eQDL_WRITE_STATUS_CONTENT:
            return eGOBI_ERR_QDL_PARSING;
      }

      return eGOBI_ERR_QDL_ERR_GENERAL;
   }

   return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   This function requests the device validate the written images

PARAMETERS:
   pImageType  [ O ] - Upon failure this may contain the type of the image
                       that failed validation
  
RETURN VALUE:
   eGobiError - Return code
===========================================================================*/
eGobiError cGobiQDLCore::ValidateQDLImages( BYTE * pImageType )
{
   if (pImageType == 0)
   {
      return eGOBI_ERR_INVALID_ARG;
   }

   *pImageType = UCHAR_MAX;

   BYTE cmd = (BYTE)eQDL_CMD_SESSION_DONE_REQ;
   eProtocolType pt = ePROTOCOL_QDL_TX;

   sSharedBuffer * pReq = 0;
   pReq = new sSharedBuffer( (const BYTE *)&cmd, 1, pt );
   if (pReq == 0)
   {
      return eGOBI_ERR_MEMORY;
   }

   sProtocolBuffer rsp = SendQDL( pReq );
   if (rsp.IsValid() == false)
   {
      return GetCorrectedLastError();
   }

   sQDLDone doneRsp( rsp.GetSharedBuffer() );
   const sQDLRawDoneRsp * pTmp = doneRsp.GetResponse();
   if (pTmp == 0)
   {
      sQDLError errRsp( rsp.GetSharedBuffer() );
      if (errRsp.IsValid() == true)
      {
         eQDLError qdlErr = errRsp.GetErrorCode();
         return GetCorrectedQDLError( qdlErr );
      }

      return eGOBI_ERR_MALFORMED_RSP;
   }

   if (doneRsp.IsSuccess() == false)
   {  
      *pImageType = pTmp->mImageType;
      switch ((eQDLDoneStatus)pTmp->mStatus)
      {
         case eQDL_DONE_STATUS_AUTH:
            return eGOBI_ERR_QDL_AUTH;

         case eQDL_DONE_STATUS_WRITE:
            return eGOBI_ERR_QDL_WRITE;
      }

      return eGOBI_ERR_QDL_ERR_GENERAL;
   }

   return eGOBI_ERR_NONE;
}

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

DESCRIPTION:
   Send a QDL request and wait for and return response (if needed)

PARAMETERS:
   pRequest          [ I ] - Request to schedule
   pAuxData          [ I ] - Auxiliary data for request
   auxDataSz         [ I ] - Size of auxiliary data
   bWaitForResponse  [ I ] - Wait for a response?

RETURN VALUE:
   sProtocolBuffer - The response (invalid when no response was received)
===========================================================================*/
sProtocolBuffer cGobiQDLCore::SendQDL(
   sSharedBuffer *            pRequest,
   const BYTE *               pAuxData,
   ULONG                      auxDataSz,
   bool                       bWaitForResponse )
{
   // Clear last error recorded
   ClearLastError();

   // Returned response
   sProtocolBuffer rsp;

   // Validate the arguments
   if (pRequest == 0)
   {
      mLastError = eGOBI_ERR_MEMORY;
      return rsp;
   }

   // We use the event based notification approach
   cSyncQueue <sProtocolNotificationEvent> evts( 12, true );   
   cProtocolQueueNotification pn( &evts );

   // Process up to the indicated timeout
   cEvent & sigEvt = evts.GetSignalEvent();

   // Build the request object
   sProtocolRequest req( pRequest, 0, mQDLTimeout, 1, 1, &pn );
   req.SetAuxiliaryData( pAuxData, auxDataSz );
   if (bWaitForResponse == false)
   {
      req.SetTXOnly();
   }

   // Are we connected?
   if ( (mQDLPortNode.empty() == true)
   ||   (mQDL.IsConnected() == false) )
   {
      mLastError = eGOBI_ERR_NO_CONNECTION;
      return rsp;
   }

   // Grab the log from the server
   const cProtocolLog & protocolLog = mQDL.GetLog();

   // Schedule the request
   ULONG reqID = mQDL.AddRequest( req );
   if (reqID == INVALID_REQUEST_ID)
   {
      mLastError = eGOBI_ERR_REQ_SCHEDULE;
      return rsp;
   }   

   bool bReq = false;
   bool bExit = false;
   DWORD idx;

   // Process up to the indicated timeout
   while (bExit == false)
   {
      int wc = sigEvt.Wait( mQDLTimeout, idx );
      if (wc == ETIME)
      {
         if (bReq == true)
         {
            mLastError = eGOBI_ERR_RESPONSE_TO;
         }
         else
         {
            mLastError = eGOBI_ERR_REQUEST_TO;
         }
         break;
      }
      else if (wc != 0)
      {
         mLastError = eGOBI_ERR_INTERNAL;
         break;
      }

      sProtocolNotificationEvent evt;
      bool bEvt = evts.GetElement( idx, evt );
      if (bEvt == false)
      {
         mLastError = eGOBI_ERR_INTERNAL;
         bExit = true;
         break;
      }

      switch (evt.mEventType)
      {
         case ePROTOCOL_EVT_REQ_ERR:
            mLastError = eGOBI_ERR_REQUEST;
            bExit = true;
            break;

         case ePROTOCOL_EVT_RSP_ERR:
            mLastError = eGOBI_ERR_RESPONSE;
            bExit = true;
            break;
            
         case ePROTOCOL_EVT_REQ_SENT:
         {
            bReq = true;
            if (bWaitForResponse == false)
            {
               // Success!
               bExit = true;
            }
         }
         break;

         case ePROTOCOL_EVT_RSP_RECV:
            // Success!
            rsp = protocolLog.GetBuffer( evt.mParam2 );
            bExit = true;
            break;
      }
   }
   
   if ( (mLastError == eGOBI_ERR_INTERNAL)
   ||   (mLastError == eGOBI_ERR_REQUEST_TO)
   ||   (mLastError == eGOBI_ERR_RESPONSE_TO) )
   {
      // Remove the request as our protocol notification object is
      // about to go out of scope and hence be destroyed
      mQDL.RemoveRequest( reqID );
   }

   return rsp;
}

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

DESCRIPTION:
   Get the device node of the currently connected Gobi device  

PARAMETERS:
   devNode     [ O ] - Device node (IE: ttyUSB0)

RETURN VALUE:
   bool
===========================================================================*/
bool cGobiQDLCore::GetConnectedPortID( std::string & devNode )
{
   // Assume failure
   bool bFound = false;

   devNode.clear();

   // Were we once connected?
   if (mQDLPortNode.size() > 0)
   {
      // Yes, but is our device still present?
      // NOTE: This does not garantee the device did not leave and come back
      std::vector <std::string> devices = GetAvailableQDLPorts();
      ULONG deviceCount = (ULONG)devices.size();

      for (ULONG a = 0; a < deviceCount; a++)
      {
         if (devices[a] == mQDLPortNode)
         {
            devNode = devices[a];

            bFound = true;
            break;
         }
      }

      if (bFound == false)
      {
         mLastError = eGOBI_ERR_NO_DEVICE;
      }
   }
   else
   {
      // We are not connected
      mLastError = eGOBI_ERR_NO_CONNECTION;
   }

   return bFound;
}
