libqmi-qmuxd/gobi-api/GobiAPI_2013-07-31-1347/Core/Socket.cpp

837 lines
21 KiB
C++

/*===========================================================================
FILE:
Socket.cpp
DESCRIPTION:
Implementation of cSocket class
PUBLIC CLASSES AND METHODS:
cSocket
This class wraps low level communication to qmuxd
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 "StdAfx.h"
#include "Socket.h"
#include "ProtocolServer.h"
#include <sys/socket.h>
#include <sys/un.h>
//---------------------------------------------------------------------------
// Definitions
//---------------------------------------------------------------------------
// Thread commands
#define START_READ_CMD 0
#define STOP_READ_CMD 1
#define EXIT_CMD 2
// Size of the QMUXD command payload
// GET_CLIENT_ID and RELEASE_CLIENT_ID must pass in a buffer of this size
#define PAYLOAD_SIZE 808
/*=========================================================================*/
// struct sQMUXDHeader
/*=========================================================================*/
#pragma pack( push, 1 )
struct sQMUXDHeader
{
/* Total size of header and following buffer */
int mTotalSize;
/* QMUXD client ID */
int mQMUXDClientID;
/* Message type */
eQMUXDMessageTypes mQMUXDMsgID;
/* Duplicate of mQMUXDClientID */
int mQMUXDClientIDDuplicate;
/* Transaction ID */
unsigned long mTxID;
/* System error code */
int mSysErrCode;
/* QMI error code (duplicate of TLV 0x02) */
int mQmiErrCode;
/* SMD channel. 0 = SMD_DATA_5 */
int mQMUXDConnectionType;
/* QMI service ID */
int mQMUXServiceID;
/* QMI client ID */
unsigned char mQMUXClientID;
/* QMI flags */
unsigned char mRxFlags;
/* In QMUXD this struct is not packed, so the compiler appends
these two bytes */
unsigned short int mMissing2Bytes;
};
#pragma pack( pop )
/*=========================================================================*/
// Free Methods
/*=========================================================================*/
/*===========================================================================
METHOD:
RxSocketThread (Free Method)
DESCRIPTION:
Thread for simulating asynchronous reads to a socket
PARAMETERS:
pData [ I ] cSocket pointer
RETURN VALUE:
void * - thread exit value (always 0)
===========================================================================*/
void * RxSocketThread( void * pData )
{
cSocket * pSocket = (cSocket*)pData;
if (pSocket == NULL || pSocket->IsValid() == false)
{
return 0;
}
fd_set inputSet, outputSet;
FD_ZERO( &inputSet );
FD_SET( pSocket->mCommandPipe[READING], &inputSet );
int largestFD = pSocket->mCommandPipe[READING];
int status = 0;
while (true)
{
// No FD_COPY() available
memcpy( &outputSet, &inputSet, sizeof( fd_set ) );
// Wait until we recieve a command or data is available
status = select( largestFD + 1, &outputSet, NULL, NULL, NULL );
if (status <= 0)
{
TRACE( "error %d in select, errno %d\n", status, errno );
break;
}
if (FD_ISSET( pSocket->mCommandPipe[READING], &outputSet ) == true)
{
// Read command value from the pipe
BYTE cmd;
status = read( pSocket->mCommandPipe[READING], &cmd, 1 );
if (status != 1)
{
TRACE( "cmd error %d\n", status );
break;
}
if (cmd == START_READ_CMD)
{
FD_SET( pSocket->mSocket, &inputSet );
largestFD = std::max( pSocket->mSocket,
pSocket->mCommandPipe[READING] );
}
else if (cmd == STOP_READ_CMD)
{
FD_CLR( pSocket->mSocket, &inputSet );
largestFD = pSocket->mCommandPipe[READING];
}
else
{
// EXIT_CMD or anything else
pSocket->mpRxCallback = 0;
break;
}
}
else if (FD_ISSET( pSocket->mSocket, &outputSet ) == true)
{
// Stop watching for read data
FD_CLR( pSocket->mSocket, &inputSet );
largestFD = pSocket->mCommandPipe[READING];
// Perform a recv for the header
sQMUXDHeader recvHdr;
status = recv( pSocket->mSocket,
&recvHdr,
sizeof( recvHdr ),
0 );
if (status != sizeof( recvHdr ))
{
TRACE( "recv error, bad size %d\n", status );
break;
}
// Calculate and read the remaining data
if ((recvHdr.mTotalSize < 0)
|| ((ULONG)recvHdr.mTotalSize < sizeof( recvHdr ))
|| ((ULONG)recvHdr.mTotalSize > sizeof( recvHdr ) + pSocket->mBuffSz))
{
TRACE( "read too large for buffer\n" );
break;
}
status = recv( pSocket->mSocket,
pSocket->mpBuffer,
recvHdr.mTotalSize - sizeof( recvHdr ),
0 );
// Is this one of our IOCTLS or a standard message?
if (recvHdr.mQMUXDMsgID == eQMUXD_MSG_WRITE_QMI_SDU)
{
cIOCallback * pCallback = pSocket->mpRxCallback;
pSocket->mpRxCallback = 0;
if (pCallback == (cIOCallback *)1)
{
// We wanted to read, but not to be notified
}
else if (status >= 0)
{
pCallback->IOComplete( 0, status );
}
else
{
pCallback->IOComplete( status, 0 );
}
}
else
{
pSocket->mpRxCallback = 0;
// Notify SendCtl() that control message completed
if (recvHdr.mQMUXDMsgID == eQMUXD_MSG_ALLOC_QMI_CLIENT_ID)
{
DWORD clientID;
memcpy( &clientID, &pSocket->mpBuffer[0], 4 );
pSocket->mCtrlMsgComplete.Set( clientID );
}
else
{
// Just set the event
pSocket->mCtrlMsgComplete.Set( 0 );
}
}
}
}
return 0;
};
/*=========================================================================*/
// cSocket Methods
/*=========================================================================*/
/*===========================================================================
METHOD:
cSocket (Public Method)
DESCRIPTION:
Constructor
RETURN VALUE:
None
===========================================================================*/
cSocket::cSocket()
: mSocket( INVALID_HANDLE_VALUE ),
mbCancelWrite( false ),
mpBuffer( 0 ),
mBuffSz( 0 ),
mRxThreadID( 0 ),
mCtrlMsgComplete(),
mQMUXDClientID( 0 ),
mQMUXClientID( 0 ),
mQMUXServiceID( 0 ),
mChannelID( -1 ),
mQMUXDTxID( 0 )
{
mCommandPipe[READING] = INVALID_HANDLE_VALUE;
mCommandPipe[WRITING] = INVALID_HANDLE_VALUE;
}
/*===========================================================================
METHOD:
~cSocket (Public Method)
DESCRIPTION:
Destructor
RETURN VALUE:
None
===========================================================================*/
cSocket::~cSocket()
{
// Disconnect from current port
Disconnect();
mCommandPipe[READING] = INVALID_HANDLE_VALUE;
mCommandPipe[WRITING] = INVALID_HANDLE_VALUE;
}
/*===========================================================================
METHOD:
IsValid (Public Method)
DESCRIPTION:
Is this object valid?
RETURN VALUE:
Bool
===========================================================================*/
bool cSocket::IsValid()
{
// Nothing to do
return true;
}
/*===========================================================================
METHOD:
Connect (Public Method)
DESCRIPTION:
Connect to the specified port
PARAMETERS:
pPort [ I ] - String that consists of "Path,Channel"
Path is path to qmux_client_socket and qmux_connect_socket
Channel is used for connection type (IE: "0" = SMD_DATA_5 )
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::Connect( LPCSTR pPort )
{
if (IsValid() == false || pPort == 0 || pPort[0] == 0)
{
return false;
}
if (mSocket != INVALID_HANDLE_VALUE)
{
Disconnect();
}
// Initialize command pipe for read thread
int nRet = pipe( mCommandPipe );
if (nRet != 0)
{
TRACE( "cSocket:Connect() pipe creation failed %d\n", nRet );
return false;
}
// Start the read thread
nRet = pthread_create( &mRxThreadID,
0,
RxSocketThread,
this );
if (nRet != 0)
{
TRACE( "cSocket::Connect() pthread_create = %d\n", nRet );
Disconnect();
return false;
}
// Create a socket
mSocket = socket( AF_UNIX, SOCK_STREAM, 0 );
if (mSocket == INVALID_HANDLE_VALUE)
{
TRACE( "unable to create socket %d\n", errno );
Disconnect();
return false;
}
// Get path and channel
std::string port = pPort;
size_t found = port.find( ',' );
if (found == std::string::npos)
{
TRACE( "Invalid port specified" );
Disconnect();
return false;
}
std::string path = port.substr( 0, found );
std::string channel = port.substr( found + 1 );
struct sockaddr_un clientSockAddr;
memset( &clientSockAddr, 0, sizeof( clientSockAddr ) );
clientSockAddr.sun_family = AF_UNIX;
// Format the client path
snprintf( &clientSockAddr.sun_path[0],
sizeof( clientSockAddr.sun_path ),
"%sqmux_client_socket%7lu",
path.c_str(),
(unsigned long)getpid() );
// Delete if it exists already
unlink( clientSockAddr.sun_path );
// Bind to a client address
nRet = bind( mSocket,
(struct sockaddr *)&clientSockAddr,
sizeof( sockaddr_un ) );
if (nRet == -1)
{
TRACE( "bad bind %d\n", errno );
Disconnect();
return false;
}
// Format the connection path
struct sockaddr_un connectSockAddr;
memset( &connectSockAddr, 0, sizeof( connectSockAddr ) );
connectSockAddr.sun_family = AF_UNIX;
snprintf( &connectSockAddr.sun_path[0],
sizeof( connectSockAddr.sun_path ),
"%sqmux_connect_socket",
path.c_str() );
// Connect to server address
nRet = connect( mSocket,
(struct sockaddr *)&connectSockAddr,
sizeof( sockaddr_un ) );
if (nRet < 0)
{
TRACE( "bad connect %d\n", errno );
Disconnect();
return false;
}
int clientID;
nRet = recv( mSocket, &clientID, sizeof( clientID ), 0 );
if (nRet != sizeof( clientID ))
{
TRACE( "bad client ID %d\n", errno );
Disconnect();
return false;
}
// Save QMUXD Client ID
mQMUXDClientID = clientID;
// Save SMD channel
mChannelID = strtol( channel.c_str(), 0, 10 );
if (mChannelID == -1)
{
Disconnect();
return false;
}
// Success!
return true;
}
/*===========================================================================
METHOD:
SendCtl (Public Method)
DESCRIPTION:
Send a control message to the lower layer
PARAMETERS:
msgType [ I ] - eQMUXDMessageType
pData [I/O] - input or output specific to ioctl request value
RETURN VALUE:
int - control message return value (0 for success)
===========================================================================*/
int cSocket::SendCtl(
UINT msgType,
void * pData )
{
if (mSocket == INVALID_HANDLE_VALUE)
{
TRACE( "Invalid file handle\n" );
return -EBADFD;
}
BYTE msg[sizeof( sQMUXDHeader ) + PAYLOAD_SIZE];
memset( &msg[0], 0, sizeof( msg ) );
// The important QMUXD header values
sQMUXDHeader * pHdr = (sQMUXDHeader *)&msg[0];
pHdr->mTotalSize = sizeof( msg );
pHdr->mQMUXDClientID = mQMUXDClientID;
pHdr->mQMUXDMsgID = (eQMUXDMessageTypes)msgType;
pHdr->mQMUXDClientIDDuplicate = mQMUXDClientID;
pHdr->mQMUXDConnectionType = mChannelID;
// mQMUXDTxID could go to INT_MAX, but rather than dealing with possible
// overflow in qmuxd or one of the lower layers, we'll stop early
mQMUXDTxID++;
if (mQMUXDTxID > 100000)
{
mQMUXDTxID = 1;
}
pHdr->mTxID = ++mQMUXDTxID;
// The Payload
BYTE * pPayload = &msg[sizeof( sQMUXDHeader )];
if (msgType == (int)eQMUXD_MSG_ALLOC_QMI_CLIENT_ID)
{
memcpy( &mQMUXServiceID, pData, 4 );
memcpy( &pPayload[0], &mQMUXServiceID, 4 );
}
else if (msgType == (int)eQMUXD_MSG_RELEASE_QMI_CLIENT_ID)
{
memcpy( &pPayload[0], &mQMUXServiceID, 4 );
memcpy( &pPayload[4], &mQMUXClientID, 4 );
}
// Send the message
int rc = send( mSocket, &msg[0], sizeof( msg ), 0 );
if (rc != sizeof( msg ))
{
TRACE( "bad write %d\n", rc );
return rc;
}
if (mpRxCallback == 0)
{
// No one is currently reading, need to trigger a read
// so our data can be recieved
RxData( &msg[0], sizeof( msg ), 0 );
}
// Wait for the response (10s timeout)
DWORD val;
rc = mCtrlMsgComplete.Wait( 10000, val );
if (rc != 0)
{
TRACE( "bad SendCtl() wait %d\n", rc );
return rc;
}
if (msgType == (int)eQMUXD_MSG_ALLOC_QMI_CLIENT_ID)
{
// Grab the client ID
mQMUXClientID = val;
// Verify client ID is valid
if (mQMUXClientID == 0)
{
return 1;
}
}
return 0;
}
/*===========================================================================
METHOD:
Disconnect (Public Method)
DESCRIPTION:
Disconnect from the current port
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::Disconnect()
{
// Assume success
bool bRC = true;
if (mCommandPipe[WRITING] != INVALID_HANDLE_VALUE)
{
if (mRxThreadID != 0)
{
// Notify the thread to exit
BYTE byte = EXIT_CMD;
write( mCommandPipe[WRITING], &byte, 1 );
// And wait for it
int nRC = pthread_join( mRxThreadID, 0 );
if (nRC != 0)
{
TRACE( "failed to join thread %d\n", nRC );
bRC = false;
}
mRxThreadID = 0;
}
close( mCommandPipe[WRITING] );
close( mCommandPipe[READING] );
mCommandPipe[READING] = INVALID_HANDLE_VALUE;
mCommandPipe[WRITING] = INVALID_HANDLE_VALUE;
}
if (mSocket != INVALID_HANDLE_VALUE)
{
close( mSocket );
mSocket = INVALID_HANDLE_VALUE;
}
// Double check
mpRxCallback = 0;
mCtrlMsgComplete.Clear();
mQMUXDClientID = 0;
mQMUXClientID = 0;
mQMUXServiceID = 0;
mQMUXDTxID = 0;
return bRC;
}
/*===========================================================================
METHOD:
CancelIO (Public Method)
DESCRIPTION:
Cancel any in-progress I/O
PARAMETERS:
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::CancelIO()
{
if (mSocket == INVALID_HANDLE_VALUE)
{
return false;
}
bool bRxCancel = CancelRx();
bool bTxCancel = CancelTx();
return (bRxCancel && bTxCancel);
}
/*===========================================================================
METHOD:
CancelRx (Public Method)
DESCRIPTION:
Cancel any in-progress receive operation
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::CancelRx()
{
if (mSocket == INVALID_HANDLE_VALUE
|| mCommandPipe[WRITING] == INVALID_HANDLE_VALUE
|| mpRxCallback == 0
|| mRxThreadID == 0)
{
TRACE( "cannot cancel, thread not active\n" );
mpRxCallback = 0;
return false;
}
// Notify the thread to stop reading
BYTE byte = STOP_READ_CMD;
int nRC = write( mCommandPipe[WRITING], &byte, 1 );
if (nRC != 1)
{
TRACE( "error %d canceling read\n", nRC );
return false;
}
// Remove the old callback
mpRxCallback = 0;
return true;
}
/*===========================================================================
METHOD:
CancelTx (Public Method)
DESCRIPTION:
Cancel any in-progress transmit operation
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::CancelTx()
{
if (mSocket == INVALID_HANDLE_VALUE)
{
return false;
}
mbCancelWrite = true;
return true;
}
/*===========================================================================
METHOD:
RxData (Public Method)
DESCRIPTION:
Receive data
PARAMETERS:
pBuf [ I ] - Buffer to contain received data
bufSz [ I ] - Amount of data to be received
pCallback [ I ] - Callback object to be exercised when the
operation completes
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::RxData(
BYTE * pBuf,
ULONG bufSz,
cIOCallback * pCallback )
{
if (IsValid() == false || mpRxCallback != 0)
{
return false;
}
if (pCallback == 0)
{
// Not interested in being notified, but we still need a value
// for this so that only one outstanding I/O operation is active
// at any given point in time
mpRxCallback = (cIOCallback * )1;
}
else
{
mpRxCallback = pCallback;
}
mpBuffer = pBuf;
mBuffSz = bufSz;
// Notify the thread to start reading
BYTE byte = START_READ_CMD;
int nRC = write( mCommandPipe[WRITING], &byte, 1 );
if (nRC != 1)
{
TRACE( "error %d starting read\n", nRC );
return false;
}
return true;
}
/*===========================================================================
METHOD:
TxData (Public Method)
DESCRIPTION:
Transmit data
PARAMETERS:
pBuf [ I ] - Data to be transmitted
bufSz [ I ] - Amount of data to be transmitted
RETURN VALUE:
bool
===========================================================================*/
bool cSocket::TxData(
const BYTE * pBuf,
ULONG bufSz )
{
if (IsValid() == false)
{
return false;
}
#ifdef DEBUG
ULONGLONG nStart = GetTickCount();
#endif
// Allow ourselves to be interupted
mbCancelWrite = false;
// Format the header
int totalSz = sizeof( sQMUXDHeader ) + bufSz;
BYTE * pMsg = new BYTE[totalSz];
if (pMsg == 0)
{
TRACE( "unable to allocate buffer\n" );
return false;
}
memset( pMsg, 0, totalSz );
// The important QMUXD header values
sQMUXDHeader * pHdr = (sQMUXDHeader *)pMsg;
pHdr->mTotalSize = totalSz;
pHdr->mQMUXDClientID = mQMUXDClientID;
pHdr->mQMUXDMsgID = eQMUXD_MSG_WRITE_QMI_SDU;
pHdr->mQMUXDClientIDDuplicate = mQMUXDClientID;
// mQMUXDTxID could go to INT_MAX, but rather than dealing with possible
// overflow in qmuxd or one of the lower layers, we'll stop early
mQMUXDTxID++;
if (mQMUXDTxID > 100000)
{
mQMUXDTxID = 1;
}
pHdr->mTxID = ++mQMUXDTxID;
pHdr->mQMUXServiceID = mQMUXServiceID;
pHdr->mQMUXClientID = mQMUXClientID;
// The data payload
memcpy( &pMsg[sizeof( sQMUXDHeader )], pBuf, bufSz );
// Send the message
int nRet = send( mSocket, pMsg, totalSz, 0 );
delete [] pMsg;
if (nRet != totalSz)
{
TRACE( "cSocket::TxData() write returned %d instead of %d\n",
nRet,
totalSz );
return false;
}
#ifdef DEBUG
TRACE( "Write of %d bytes took %llu milliseconds\n",
totalSz,
GetTickCount() - nStart );
#endif
return true;
}