#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "CommonServices.h"
#include <process.h>
#include "RMxCommon.h"
#ifdef __cplusplus
extern "C" {
#endif
#define DEBUG_NAME "[RMxCommon] "
#define kRMxSessionOpenValidFlags ( kRMxSessionFlagsNoThread | kRMxSessionFlagsNoClose )
#if 0
#pragma mark == Prototypes ==
#endif
DEBUG_LOCAL unsigned WINAPI RMxSessionThread( LPVOID inParam );
DEBUG_LOCAL OSStatus RMxSessionInitServer( RMxSessionRef inSession );
DEBUG_LOCAL OSStatus RMxSessionInitClient( RMxSessionRef inSession, const char *inServer );
DEBUG_LOCAL OSStatus RMxSessionConnect( RMxSessionRef inSession, const struct sockaddr *inAddr, size_t inAddrSize );
DEBUG_LOCAL OSStatus
RMxSessionSendMessageVAList(
RMxSessionRef inSession,
RMxOpCode inOpCode,
OSStatus inStatus,
const char * inFormat,
va_list inArgs1,
va_list inArgs2 );
#if 0
#pragma mark == Globals ==
#endif
DEBUG_LOCAL CRITICAL_SECTION gRMxLock;
DEBUG_LOCAL bool gRMxLockInitialized = false;
DEBUG_LOCAL RMxSessionRef gRMxSessionList = NULL;
RMxState gRMxState = kRMxStateInvalid;
HANDLE gRMxStateChangeEvent = NULL;
#if 0
#pragma mark -
#pragma mark == General ==
#endif
OSStatus RMxInitialize( void )
{
OSStatus err;
WSADATA wsaData;
err = WSAStartup( MAKEWORD( 2, 2 ), &wsaData );
require_noerr( err, exit );
require_action( ( LOBYTE( wsaData.wVersion ) == 2 ) && ( HIBYTE( wsaData.wVersion ) == 2 ), exit, err = kUnsupportedErr );
InitializeCriticalSection( &gRMxLock );
gRMxLockInitialized = true;
gRMxState = kRMxStateInvalid;
gRMxStateChangeEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
err = translate_errno( gRMxStateChangeEvent, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
exit:
return( err );
}
void RMxFinalize( void )
{
BOOL ok;
OSStatus err;
gRMxState = kRMxStateStop;
if( gRMxStateChangeEvent )
{
ok = SetEvent( gRMxStateChangeEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
}
while( gRMxSessionList )
{
err = RMxSessionClose( gRMxSessionList, kEndingErr );
check( ( err == kNoErr ) || ( err == kNotFoundErr ) );
}
if( gRMxStateChangeEvent )
{
ok = CloseHandle( gRMxStateChangeEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
gRMxStateChangeEvent = NULL;
}
if( gRMxLockInitialized )
{
gRMxLockInitialized = false;
DeleteCriticalSection( &gRMxLock );
}
WSACleanup();
}
void RMxLock( void )
{
check( gRMxLockInitialized );
if( gRMxLockInitialized )
{
EnterCriticalSection( &gRMxLock );
}
}
void RMxUnlock( void )
{
check( gRMxLockInitialized );
if( gRMxLockInitialized )
{
LeaveCriticalSection( &gRMxLock );
}
}
#if 0
#pragma mark -
#pragma mark == Messages ==
#endif
void RMxMessageInitialize( RMxMessage *inMessage )
{
check( inMessage );
if( inMessage )
{
inMessage->sendSize = 0;
inMessage->sendData = NULL;
inMessage->recvSize = 0;
inMessage->recvData = NULL;
inMessage->bufferSize = 0;
inMessage->buffer = NULL;
}
}
void RMxMessageRelease( RMxMessage *inMessage )
{
check( inMessage );
if( inMessage )
{
check( !inMessage->recvData ||
( inMessage->recvSize > sizeof( inMessage->storage ) ) ||
( inMessage->recvData == inMessage->storage ) );
if( inMessage->recvData && ( inMessage->recvData != inMessage->storage ) )
{
free( inMessage->recvData );
}
inMessage->recvData = NULL;
}
}
#if 0
#pragma mark -
#pragma mark == Sessions ==
#endif
OSStatus
RMxSessionOpen(
const char * inServer,
RMxSessionFlags inFlags,
SocketRef inSock,
RMxMessageCallBack inCallBack,
void * inContext,
RMxSessionRef * outSession,
RMxOpCode inMessageOpCode,
const char * inMessageFormat,
... )
{
OSStatus err;
RMxSessionRef session;
DWORD result;
bool locked;
session = NULL;
locked = false;
dlog( kDebugLevelNotice, DEBUG_NAME "opening %s session to %s\n", IsValidSocket( inSock ) ? "server" : "client",
inServer ? inServer : "<local>" );
require_action( gRMxState == kRMxStateRun, exit, err = kStateErr );
require_action( ( inFlags & ~kRMxSessionOpenValidFlags ) == 0, exit, err = kFlagErr );
session = (RMxSessionRef) calloc( 1, sizeof( *session ) );
require_action( session, exit, err = kNoMemoryErr );
session->flags = inFlags;
session->sock = kInvalidSocketRef;
session->callback = inCallBack;
RMxMessageInitialize( &session->message );
session->message.context = inContext;
session->message.session = session;
session->messageRecvBuffer = session->message.storage;
session->messageRecvBufferPtr = session->messageRecvBuffer;
session->messageRecvRemaining = kRMxMessageHeaderSize;
session->messageRecvHeaderDone = false;
RMxLock();
session->next = gRMxSessionList;
gRMxSessionList = session;
RMxUnlock();
session->closeEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
err = translate_errno( session->closeEvent, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
if( IsValidSocket( inSock ) )
{
session->flags |= kRMxSessionFlagsServer;
session->sock = inSock;
inSock = kInvalidSocketRef;
err = RMxSessionInitServer( session );
require_noerr( err, exit );
}
else
{
err = RMxSessionInitClient( session, inServer );
require_noerr( err, exit );
}
RMxLock();
locked = true;
if( !( inFlags & kRMxSessionFlagsNoThread ) )
{
session->thread = (HANDLE) _beginthreadex_compat( NULL, 0, RMxSessionThread, session, CREATE_SUSPENDED, &session->threadID );
err = translate_errno( session->thread, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
result = ResumeThread( session->thread );
err = translate_errno( result != (DWORD) -1, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
}
if( inMessageFormat )
{
va_list args1;
va_list args2;
va_start( args1, inMessageFormat );
va_start( args2, inMessageFormat );
err = RMxSessionSendMessageVAList( session, inMessageOpCode, kNoErr, inMessageFormat, args1, args2 );
va_end( args1 );
va_end( args2 );
require_noerr( err, exit );
}
if( outSession )
{
*outSession = session;
}
session = NULL;
exit:
if( locked )
{
RMxUnlock();
}
if( session )
{
RMxSessionClose( session, err );
}
if( IsValidSocket( inSock ) )
{
close_compat( inSock );
}
return( err );
}
OSStatus RMxSessionClose( RMxSessionRef inSession, OSStatus inReason )
{
OSStatus err;
bool locked;
RMxSessionRef * p;
bool sameThread;
bool deferClose;
BOOL ok;
DWORD threadID;
DWORD result;
DEBUG_USE_ONLY( inReason );
check( inSession );
RMxLock();
locked = true;
for( p = &gRMxSessionList; *p; p = &( *p )->next )
{
if( *p == inSession )
{
break;
}
}
require_action( *p, exit, err = kNotFoundErr );
deferClose = false;
threadID = GetCurrentThreadId();
sameThread = inSession->thread && ( threadID == inSession->threadID );
if( sameThread && !( inSession->flags & kRMxSessionFlagsThreadDone ) )
{
inSession->flags &= ~kRMxSessionFlagsNoClose;
deferClose = true;
}
if( !sameThread && ( inSession->flags & kRMxSessionFlagsThreadDone ) && !( inSession->flags & kRMxSessionFlagsNoClose ) )
{
deferClose = true;
}
inSession->quit = true;
if( inSession->closeEvent )
{
ok = SetEvent( inSession->closeEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
}
if( deferClose )
{
err = kNoErr;
goto exit;
}
inSession->flags |= kRMxSessionFlagsNoClose;
*p = inSession->next;
RMxUnlock();
locked = false;
if( inSession->thread && ( threadID != inSession->threadID ) )
{
result = WaitForSingleObject( inSession->thread, 10 * 1000 );
check_translated_errno( result == WAIT_OBJECT_0, (OSStatus) GetLastError(), result );
}
if( inSession->thread )
{
ok = CloseHandle( inSession->thread );
check_translated_errno( ok, errno_compat(), kUnknownErr );
inSession->thread = NULL;
}
if( inSession->sockEvent )
{
ok = CloseHandle( inSession->sockEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
inSession->sockEvent = NULL;
}
if( IsValidSocket( inSession->sock ) )
{
err = close_compat( inSession->sock );
err = translate_errno( err == 0, errno_compat(), kUnknownErr );
check_noerr( err );
inSession->sock = kInvalidSocketRef;
}
if( inSession->closeEvent )
{
ok = CloseHandle( inSession->closeEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
inSession->closeEvent = NULL;
}
RMxMessageRelease( &inSession->message );
free( inSession );
err = kNoErr;
dlog( kDebugLevelNotice, DEBUG_NAME "session closed (%d %m)\n", inReason, inReason );
exit:
if( locked )
{
RMxUnlock();
}
return( err );
}
DEBUG_LOCAL unsigned WINAPI RMxSessionThread( LPVOID inParam )
{
OSStatus err;
RMxSessionRef session;
bool safeToClose;
session = (RMxSessionRef) inParam;
check( session );
for( ;; )
{
if( session->quit || ( gRMxState != kRMxStateRun ) )
{
dlog( kDebugLevelNotice, DEBUG_NAME "session state exit (quit=%d, state=%d)\n", session->quit, gRMxState );
err = kEndingErr;
break;
}
err = RMxSessionRecvMessage( session, INFINITE );
if( err == kNoErr )
{
if( session->callback )
{
session->callback( &session->message );
}
RMxMessageRelease( &session->message );
}
else
{
dlog( kDebugLevelNotice, DEBUG_NAME "session closing (%d %m)\n", err, err );
break;
}
}
if( session->callback )
{
session->message.opcode = kRMxOpCodeInvalid;
session->message.status = kNoErr;
session->message.sendSize = 0;
session->message.sendData = NULL;
session->message.recvSize = 0;
session->message.recvData = NULL;
session->callback( &session->message );
}
RMxLock();
session->flags |= kRMxSessionFlagsThreadDone;
safeToClose = !( session->flags & kRMxSessionFlagsNoClose );
RMxUnlock();
if( safeToClose )
{
RMxSessionClose( session, err );
}
_endthreadex_compat( (unsigned) err );
return( (unsigned) err );
}
DEBUG_LOCAL OSStatus RMxSessionInitServer( RMxSessionRef inSession )
{
OSStatus err;
inSession->sockEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
err = translate_errno( inSession->sockEvent, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
err = WSAEventSelect( inSession->sock, inSession->sockEvent, FD_READ | FD_CLOSE );
err = translate_errno( err == 0, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
inSession->waitCount = 0;
inSession->waitHandles[ inSession->waitCount++ ] = inSession->sockEvent;
inSession->waitHandles[ inSession->waitCount++ ] = inSession->closeEvent;
inSession->waitHandles[ inSession->waitCount++ ] = gRMxStateChangeEvent;
check( inSession->waitCount == sizeof_array( inSession->waitHandles ) );
exit:
return( err );
}
DEBUG_LOCAL OSStatus RMxSessionInitClient( RMxSessionRef inSession, const char *inServer )
{
OSStatus err;
struct addrinfo hints;
struct addrinfo * addrList;
struct addrinfo * addr;
BOOL ok;
addrList = NULL;
memset( &hints, 0, sizeof( hints ) );
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
err = getaddrinfo( inServer, kRMxServerPortString, &hints, &addrList );
require_noerr( err, exit );
for( addr = addrList; addr; addr = addr->ai_next )
{
check( addr->ai_addr && ( addr->ai_addrlen > 0 ) );
if( inSession->quit || ( gRMxState != kRMxStateRun ) )
{
dlog( kDebugLevelNotice, DEBUG_NAME "session state exit while connecting (quit=%d, state=%d)\n",
inSession->quit, gRMxState );
err = kEndingErr;
break;
}
if( IsValidSocket( inSession->sock ) )
{
err = close_compat( inSession->sock );
check_translated_errno( err == 0, errno_compat(), kUnknownErr );
inSession->sock = kInvalidSocketRef;
}
if( inSession->sockEvent )
{
ok = CloseHandle( inSession->sockEvent );
check_translated_errno( ok, errno_compat(), kUnknownErr );
inSession->sockEvent = NULL;
}
dlog( kDebugLevelTrace, DEBUG_NAME "connecting %s socket to %s/%##a\n",
( addr->ai_family == AF_INET ) ? "AF_INET" : ( addr->ai_family == AF_INET6 ) ? "AF_INET6" : "<unknown>",
inServer ? inServer : "<local>", addr->ai_addr );
inSession->sock = socket( addr->ai_family, addr->ai_socktype, addr->ai_protocol );
err = translate_errno( IsValidSocket( inSession->sock ), errno_compat(), kNoResourcesErr );
if( err != kNoErr )
{
dlog( kDebugLevelNotice, DEBUG_NAME "%s socket not supported...skipping (%d %m)\n",
( addr->ai_family == AF_INET ) ? "AF_INET" : ( addr->ai_family == AF_INET6 ) ? "AF_INET6" : "<unknown>",
err, err );
continue;
}
inSession->sockEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
err = translate_errno( inSession->sockEvent, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
err = WSAEventSelect( inSession->sock, inSession->sockEvent, FD_READ | FD_CONNECT | FD_CLOSE );
err = translate_errno( err == 0, errno_compat(), kNoResourcesErr );
require_noerr( err, exit );
inSession->waitCount = 0;
inSession->waitHandles[ inSession->waitCount++ ] = inSession->sockEvent;
inSession->waitHandles[ inSession->waitCount++ ] = inSession->closeEvent;
inSession->waitHandles[ inSession->waitCount++ ] = gRMxStateChangeEvent;
check( inSession->waitCount == sizeof_array( inSession->waitHandles ) );
err = RMxSessionConnect( inSession, addr->ai_addr, addr->ai_addrlen );
if( err == kNoErr )
{
break;
}
}
require_action( addr, exit, err = kConnectionErr );
exit:
if( addrList )
{
freeaddrinfo( addrList );
}
return( err );
}
DEBUG_LOCAL OSStatus RMxSessionConnect( RMxSessionRef inSession, const struct sockaddr *inAddr, size_t inAddrSize )
{
OSStatus err;
DWORD result;
check( inSession );
check( inSession->sock );
check( inSession->waitCount > 0 );
check( inAddr && ( inAddrSize > 0 ) );
err = connect( inSession->sock, inAddr, (int) inAddrSize );
if( err == 0 ) goto exit;
result = WaitForMultipleObjects( inSession->waitCount, inSession->waitHandles, FALSE, kRMxClientTimeout );
if( result == WAIT_OBJECT_0 ) {
WSANETWORKEVENTS events;
err = WSAEnumNetworkEvents( inSession->sock, NULL, &events );
require_noerr( err, exit );
require_action( events.lNetworkEvents & FD_CONNECT, exit, err = kSelectorErr );
err = events.iErrorCode[ FD_CONNECT_BIT ];
}
else if( result == WAIT_TIMEOUT )
{
err = kTimeoutErr;
}
else
{
err = kEndingErr;
}
exit:
return( err );
}
OSStatus RMxSessionSendMessage( RMxSessionRef inSession, RMxOpCode inOpCode, OSStatus inStatus, const char *inFormat, ... )
{
OSStatus err;
va_list args1;
va_list args2;
va_start( args1, inFormat );
va_start( args2, inFormat );
err = RMxSessionSendMessageVAList( inSession, inOpCode, inStatus, inFormat, args1, args2 );
va_end( args1 );
va_end( args2 );
require_noerr( err, exit );
exit:
return( err );
}
DEBUG_LOCAL OSStatus
RMxSessionSendMessageVAList(
RMxSessionRef inSession,
RMxOpCode inOpCode,
OSStatus inStatus,
const char * inFormat,
va_list inArgs1,
va_list inArgs2 )
{
OSStatus err;
RMxMessage msg;
uint8_t * bufferStorage;
uint8_t * buffer;
size_t bodySize;
size_t headerSize;
size_t size;
int n;
bufferStorage = NULL;
check( inSession );
check( IsValidSocket( inSession->sock ) );
check( inFormat );
err = RMxPackedSizeVAList( &bodySize, inFormat, inArgs1 );
require_noerr( err, exit );
size = kRMxMessageHeaderSize + bodySize;
if( size <= sizeof( msg.storage ) )
{
buffer = msg.storage;
}
else
{
bufferStorage = (uint8_t *) malloc( size );
require_action( bufferStorage, exit, err = kNoMemoryErr );
buffer = bufferStorage;
}
err = RMxPack( buffer, kRMxMessageHeaderSize, &headerSize, "wwwwww",
kRMxSignatureVersion1, inOpCode, kRMxFlagsNone, 0, inStatus, (uint32_t) bodySize ); require_noerr( err, exit );
check( headerSize == kRMxMessageHeaderSize );
err = RMxPackVAList( buffer + kRMxMessageHeaderSize, size - kRMxMessageHeaderSize, &size, inFormat, inArgs2 );
require_noerr( err, exit );
check( size == bodySize );
size = headerSize + size;
n = send( inSession->sock, (const char *) buffer, (int) size, 0 );
err = translate_errno( n == (int) size, errno_compat(), kWriteErr );
require_noerr( err, exit );
exit:
if( bufferStorage )
{
free( bufferStorage );
}
return( err );
}
OSStatus RMxSessionRecvMessage( RMxSessionRef inSession, DWORD inTimeout )
{
OSStatus err;
DWORD result;
int n;
for( ;; )
{
if( inSession->quit || ( gRMxState != kRMxStateRun ) )
{
dlog( kDebugLevelNotice, DEBUG_NAME "session recv state exit (quit=%d, state=%d)\n", inSession->quit, gRMxState );
err = kEndingErr;
goto exit;
}
result = WaitForMultipleObjects( inSession->waitCount, inSession->waitHandles, FALSE, inTimeout );
if( result == WAIT_OBJECT_0 ) {
n = recv( inSession->sock, (char *) inSession->messageRecvBufferPtr, inSession->messageRecvRemaining, 0 );
if( n > 0 )
{
inSession->messageRecvBufferPtr += n;
inSession->messageRecvRemaining -= n;
if( inSession->messageRecvRemaining == 0 )
{
if( !inSession->messageRecvHeaderDone )
{
err = RMxUnpack( inSession->messageRecvBuffer, kRMxMessageHeaderSize,
"wwwwww",
&inSession->message.signature,
&inSession->message.opcode,
&inSession->message.flags,
&inSession->message.xid,
&inSession->message.status,
&inSession->message.recvSize );
require_noerr( err, exit );
require_action( inSession->message.signature == kRMxSignatureVersion1, exit, err = kMismatchErr );
require_action( inSession->message.opcode != kRMxOpCodeInvalid, exit, err = kMismatchErr );
inSession->messageRecvHeaderDone = true;
if( inSession->message.recvSize > 0 )
{
if( inSession->message.recvSize <= sizeof( inSession->message.storage ) )
{
inSession->message.recvData = inSession->message.storage;
}
else
{
inSession->message.recvData = (uint8_t *) malloc( inSession->message.recvSize );
require_action( inSession->message.recvData, exit, err = kNoMemoryErr );
}
inSession->messageRecvBufferPtr = inSession->message.recvData;
inSession->messageRecvRemaining = (int) inSession->message.recvSize;
}
}
}
if( inSession->messageRecvHeaderDone && ( inSession->messageRecvRemaining == 0 ) )
{
inSession->messageRecvBufferPtr = inSession->messageRecvBuffer;
inSession->messageRecvRemaining = kRMxMessageHeaderSize;
inSession->messageRecvHeaderDone = false;
break;
}
}
else
{
err = errno_compat();
dlog( kDebugLevelNotice, DEBUG_NAME "session recv peer disconnected (%d/%d %m)\n", n, err, err );
err = kConnectionErr;
goto exit;
}
}
else if( result == ( WAIT_OBJECT_0 + 1 ) ) {
dlog( kDebugLevelNotice, DEBUG_NAME "session recv close signaled\n" );
err = kEndingErr;
goto exit;
}
else if( result == ( WAIT_OBJECT_0 + 2 ) ) {
dlog( kDebugLevelNotice, DEBUG_NAME "session recv state change (%d)\n", gRMxState );
err = kEndingErr;
goto exit;
}
else if( result == WAIT_TIMEOUT ) {
dlog( kDebugLevelNotice, DEBUG_NAME "session recv timeout\n" );
err = kTimeoutErr;
goto exit;
}
else
{
err = errno_compat();
dlog( kDebugLevelAlert, DEBUG_NAME "session recv message wait error: 0x%08X, %d (%m)\n", result, err, err );
err = (OSStatus) result;
goto exit;
}
}
err = kNoErr;
exit:
return( err );
}
#if 0
#pragma mark -
#pragma mark == Utilities ==
#endif
OSStatus
RMxCheckVersion(
uint32_t inClientCurrentVersion, uint32_t inClientOldestClientVersion, uint32_t inClientOldestServerVersion,
uint32_t inServerCurrentVersion, uint32_t inServerOldestClientVersion, uint32_t inServerOldestServerVersion )
{
OSStatus err;
const char * message;
DEBUG_USE_ONLY( inClientOldestClientVersion );
DEBUG_USE_ONLY( inServerOldestServerVersion );
DEBUG_USE_ONLY( message );
if( inClientCurrentVersion == inServerCurrentVersion )
{
message = "versions exactly match";
err = kNoErr;
}
else if( inClientCurrentVersion > inServerCurrentVersion )
{
if( inClientOldestServerVersion <= inServerCurrentVersion )
{
message = "client newer, but compatible";
err = kNoErr;
}
else
{
message = "server too old for client";
err = kIncompatibleErr;
}
}
else
{
if( inServerOldestClientVersion <= inClientCurrentVersion )
{
message = "server newer, but compatible";
err = kNoErr;
}
else
{
message = "client too old for server";
err = kIncompatibleErr;
}
}
dlog( kDebugLevelNotice, DEBUG_NAME "%s (client=%v/%v/%v vs server=%v/%v/%v)\n", message,
inClientCurrentVersion, inClientOldestClientVersion, inClientOldestServerVersion,
inServerCurrentVersion, inServerOldestClientVersion, inServerOldestServerVersion );
return( err );
}
OSStatus RMxPackedSize( size_t *outSize, const char *inFormat, ... )
{
OSStatus err;
va_list args;
va_start( args, inFormat );
err = RMxPackedSizeVAList( outSize, inFormat, args );
va_end( args );
return( err );
}
OSStatus RMxPackedSizeVAList( size_t *outSize, const char *inFormat, va_list inArgs )
{
OSStatus err;
size_t size;
char c;
const uint8_t * src;
uint32_t tempU32;
const uint8_t * p;
check_compile_time_code( sizeof( unsigned int ) >= 4 );
size = 0;
for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
{
switch( c )
{
case 'b':
va_arg( inArgs, unsigned int );
size += 1;
break;
case 'h':
va_arg( inArgs, unsigned int );
size += 2;
break;
case 'w':
va_arg( inArgs, unsigned int );
size += 4;
break;
case 's':
src = va_arg( inArgs, const uint8_t * );
check( src );
p = src;
while( *p++ != 0 ) {}
size += ( p - src );
break;
case 'n':
tempU32 = (uint32_t) va_arg( inArgs, unsigned int );
src = va_arg( inArgs, const uint8_t * );
check( src || ( tempU32 == 0 ) );
size += ( 4 + tempU32 );
break;
case ' ': break;
default:
err = kUnsupportedErr;
goto exit;
}
}
if( outSize )
{
*outSize = size;
}
err = kNoErr;
exit:
return( err );
}
OSStatus RMxPack( void *inBuffer, size_t inMaxSize, size_t *outSize, const char *inFormat, ... )
{
OSStatus err;
va_list args;
va_start( args, inFormat );
err = RMxPackVAList( inBuffer, inMaxSize, outSize, inFormat, args );
va_end( args );
return( err );
}
OSStatus RMxPackVAList( void *inBuffer, size_t inMaxSize, size_t *outSize, const char *inFormat, va_list inArgs )
{
OSStatus err;
char c;
const uint8_t * src;
uint8_t * dst;
uint8_t * end;
uint8_t tempU8;
uint16_t tempU16;
uint32_t tempU32;
const uint8_t * p;
size_t size;
check_compile_time_code( sizeof( unsigned int ) >= 4 );
dst = (uint8_t *) inBuffer;
end = dst + inMaxSize;
for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
{
switch( c )
{
case 'b':
check( ( end - dst ) >= 1 );
tempU8 = (uint8_t) va_arg( inArgs, unsigned int );
*dst++ = tempU8;
break;
case 'h':
check( ( end - dst ) >= 2 );
tempU16 = (uint16_t) va_arg( inArgs, unsigned int );
*dst++ = (uint8_t)( ( tempU16 >> 8 ) & 0xFF );
*dst++ = (uint8_t)( tempU16 & 0xFF );
break;
case 'w':
check( ( end - dst ) >= 4 );
tempU32 = (uint32_t) va_arg( inArgs, unsigned int );
*dst++ = (uint8_t)( ( tempU32 >> 24 ) & 0xFF );
*dst++ = (uint8_t)( ( tempU32 >> 16 ) & 0xFF );
*dst++ = (uint8_t)( ( tempU32 >> 8 ) & 0xFF );
*dst++ = (uint8_t)( tempU32 & 0xFF );
break;
case 's':
src = va_arg( inArgs, const uint8_t * );
check( src );
p = src;
while( *p++ != 0 ) {}
size = (size_t)( p - src );
check( ( end - dst ) >= (ptrdiff_t) size );
while( size-- > 0 )
{
*dst++ = *src++;
}
break;
case 'n':
tempU32 = (uint32_t) va_arg( inArgs, unsigned int );
check( ( end - dst ) >= (ptrdiff_t)( 4 + tempU32 ) );
src = va_arg( inArgs, const uint8_t * );
check( src || ( tempU32 == 0 ) );
*dst++ = (uint8_t)( ( tempU32 >> 24 ) & 0xFF );
*dst++ = (uint8_t)( ( tempU32 >> 16 ) & 0xFF );
*dst++ = (uint8_t)( ( tempU32 >> 8 ) & 0xFF );
*dst++ = (uint8_t)( tempU32 & 0xFF );
while( tempU32-- > 0 )
{
*dst++ = *src++;
}
break;
case ' ': break;
default:
err = kUnsupportedErr;
goto exit;
}
}
if( outSize )
{
*outSize = (size_t)( dst - ( (uint8_t *) inBuffer ) );
}
err = kNoErr;
exit:
return( err );
}
OSStatus RMxUnpack( const void *inData, size_t inSize, const char *inFormat, ... )
{
OSStatus err;
va_list args;
va_start( args, inFormat );
err = RMxUnpackVAList( inData, inSize, inFormat, args );
va_end( args );
return( err );
}
OSStatus RMxUnpackVAList( const void *inData, size_t inSize, const char *inFormat, va_list inArgs )
{
OSStatus err;
char c;
const uint8_t * src;
const uint8_t * end;
uint8_t * b;
uint16_t * h;
uint32_t * w;
uint16_t tempU16;
uint32_t tempU32;
const uint8_t * p;
size_t size;
const uint8_t ** ptrArg;
size_t * sizeArg;
check_compile_time_code( sizeof( unsigned int ) >= 4 );
src = (const uint8_t *) inData;
end = src + inSize;
for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
{
switch( c )
{
case 'b':
require_action( ( end - src ) >= 1, exit, err = kSizeErr );
b = va_arg( inArgs, uint8_t * );
if( b )
{
*b = *src;
}
++src;
break;
case 'h':
require_action( ( end - src ) >= 2, exit, err = kSizeErr );
tempU16 = (uint16_t)( *src++ << 8 );
tempU16 |= (uint16_t)( *src++ );
h = va_arg( inArgs, uint16_t * );
if( h )
{
*h = tempU16;
}
break;
case 'w':
require_action( ( end - src ) >= 4, exit, err = kSizeErr );
tempU32 = (uint32_t)( *src++ << 24 );
tempU32 |= (uint32_t)( *src++ << 16 );
tempU32 |= (uint32_t)( *src++ << 8 );
tempU32 |= (uint32_t)( *src++ );
w = va_arg( inArgs, uint32_t * );
if( w )
{
*w = tempU32;
}
break;
case 's':
p = src;
while( ( ( end - p ) > 0 ) && ( *p != 0 ) )
{
++p;
}
require_action( ( end - p ) > 0, exit, err = kSizeErr );
size = (size_t)( p - src );
ptrArg = va_arg( inArgs, const uint8_t ** );
if( ptrArg )
{
*ptrArg = src;
}
sizeArg = va_arg( inArgs, size_t * );
if( sizeArg )
{
*sizeArg = size;
}
src = p + 1;
break;
case 'n':
require_action( ( end - src ) >= 4, exit, err = kSizeErr );
tempU32 = (uint32_t)( *src++ << 24 );
tempU32 |= (uint32_t)( *src++ << 16 );
tempU32 |= (uint32_t)( *src++ << 8 );
tempU32 |= (uint32_t)( *src++ );
require_action( ( end - src ) >= (ptrdiff_t) tempU32, exit, err = kSizeErr );
size = (size_t) tempU32;
ptrArg = va_arg( inArgs, const uint8_t ** );
if( ptrArg )
{
*ptrArg = src;
}
sizeArg = va_arg( inArgs, size_t * );
if( sizeArg )
{
*sizeArg = size;
}
src += size;
break;
case ' ': break;
default:
err = kUnsupportedErr;
goto exit;
}
}
err = kNoErr;
exit:
return( err );
}
#if( DEBUG )
OSStatus RMxPackUnpackTest( void );
OSStatus RMxPackUnpackTest( void )
{
static const uint8_t data[] =
{
0xAA,
0xBB, 0xCC,
0x11, 0x22, 0x33, 0x44,
0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x70, 0x65, 0x6F, 0x70, 0x6C, 0x65, 0x00, 0x00, 0x00, 0x00, 0x05, 0x74, 0x65, 0x73, 0x74, 0x73 };
OSStatus err;
uint8_t buffer[ 128 ];
size_t size;
uint8_t b;
uint16_t h;
uint32_t w;
char * s;
size_t sSize;
uint8_t * d;
size_t dSize;
check_compile_time_code( sizeof( data ) >= 29 );
printf( "\nsimple API test\n" );
err = RMxPackedSize( &size, "bhwsn ", 0xAA, 0xBBCC, 0x11223344, "hello people", 5, "tests" );
require_noerr( err, exit );
require_action( size == 29, exit, err = kSizeErr );
err = RMxPack( buffer, 29, &size, " bhwsn", 0xAA, 0xBBCC, 0x11223344, "hello people", 5, "tests" );
require_noerr( err, exit );
require_action( size == 29, exit, err = kSizeErr );
require_action( memcmp( buffer, data, size ) == 0, exit, err = kMismatchErr );
err = RMxUnpack( data, sizeof( data ), "bhw sn", &b, &h, &w, &s, &sSize, &d, &dSize );
require_noerr( err, exit );
require_action( b == 0xAA, exit, err = kMismatchErr );
require_action( h == 0xBBCC, exit, err = kMismatchErr );
require_action( w == 0x11223344, exit, err = kMismatchErr );
require_action( sSize == 12, exit, err = kSizeErr );
require_action( strcmp( s, "hello people" ) == 0, exit, err = kMismatchErr );
require_action( dSize == 5, exit, err = kSizeErr );
require_action( memcmp( d, "tests", 5 ) == 0, exit, err = kMismatchErr );
printf( "\nsimple API test done\n\n" );
printf( "\n\nALL TESTS PASSED\n\n" );
exit:
return( err );
}
#endif // DEBUG
#ifdef __cplusplus
}
#endif