2 * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
8 * This file contains Original Code and/or Modifications of Original Code
9 * as defined in and that are subject to the Apple Public Source License
10 * Version 2.0 (the 'License'). You may not use this file except in
11 * compliance with the License. Please obtain a copy of the License at
12 * http://www.opensource.apple.com/apsl/ and read it before using this
15 * The Original Code and all software distributed under the License are
16 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
17 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
18 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
19 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
20 * Please see the License for the specific language governing rights and
21 * limitations under the License.
23 * @APPLE_LICENSE_HEADER_END@
25 Change History (most recent first):
28 Revision 1.2 2004/03/16 22:09:03 bradley
29 Skip socket creation failures to handle local IPv6 addresses being returned by Windows even when
30 they are not actually supported by the OS; Log a message and only fail if no sockets can be created.
32 Revision 1.1 2004/01/30 02:35:13 bradley
33 Rendezvous Message Exchange implementation for DNS-SD IPC on Windows.
41 #include "CommonServices.h"
45 #include "RMxCommon.h"
51 //===========================================================================================================================
53 //===========================================================================================================================
55 #define DEBUG_NAME "[RMxCommon] "
57 #define kRMxSessionOpenValidFlags ( kRMxSessionFlagsNoThread | kRMxSessionFlagsNoClose )
60 #pragma mark == Prototypes ==
63 //===========================================================================================================================
65 //===========================================================================================================================
69 DEBUG_LOCAL
unsigned WINAPI
RMxSessionThread( LPVOID inParam
);
70 DEBUG_LOCAL OSStatus
RMxSessionInitServer( RMxSessionRef inSession
);
71 DEBUG_LOCAL OSStatus
RMxSessionInitClient( RMxSessionRef inSession
, const char *inServer
);
72 DEBUG_LOCAL OSStatus
RMxSessionConnect( RMxSessionRef inSession
, const struct sockaddr
*inAddr
, size_t inAddrSize
);
74 RMxSessionSendMessageVAList(
75 RMxSessionRef inSession
,
78 const char * inFormat
,
83 #pragma mark == Globals ==
86 //===========================================================================================================================
88 //===========================================================================================================================
92 DEBUG_LOCAL CRITICAL_SECTION gRMxLock
;
93 DEBUG_LOCAL
bool gRMxLockInitialized
= false;
94 DEBUG_LOCAL RMxSessionRef gRMxSessionList
= NULL
;
95 RMxState gRMxState
= kRMxStateInvalid
;
96 HANDLE gRMxStateChangeEvent
= NULL
;
100 #pragma mark == General ==
103 //===========================================================================================================================
105 //===========================================================================================================================
107 OSStatus
RMxInitialize( void )
114 err
= WSAStartup( MAKEWORD( 2, 2 ), &wsaData
);
115 require_noerr( err
, exit
);
116 require_action( ( LOBYTE( wsaData
.wVersion
) == 2 ) && ( HIBYTE( wsaData
.wVersion
) == 2 ), exit
, err
= kUnsupportedErr
);
118 // Set up the global locked used to protect things like the session list.
120 InitializeCriticalSection( &gRMxLock
);
121 gRMxLockInitialized
= true;
123 // Set up the state and state changed event. A manual-reset event is used so all threads wake up when it is signaled.
125 gRMxState
= kRMxStateInvalid
;
126 gRMxStateChangeEvent
= CreateEvent( NULL
, TRUE
, FALSE
, NULL
);
127 err
= translate_errno( gRMxStateChangeEvent
, errno_compat(), kNoResourcesErr
);
128 require_noerr( err
, exit
);
134 //===========================================================================================================================
136 //===========================================================================================================================
138 void RMxFinalize( void )
143 // Signal a state changed event to trigger everything to stop. Note: This is a manual-reset event so it will
144 // remain signaled to allow all running threads to wake up and exit cleanly.
146 gRMxState
= kRMxStateStop
;
147 if( gRMxStateChangeEvent
)
149 ok
= SetEvent( gRMxStateChangeEvent
);
150 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
153 // Close any open sessions.
155 while( gRMxSessionList
)
157 err
= RMxSessionClose( gRMxSessionList
, kEndingErr
);
158 check( ( err
== kNoErr
) || ( err
== kNotFoundErr
) );
161 // Release the state changed event.
163 if( gRMxStateChangeEvent
)
165 ok
= CloseHandle( gRMxStateChangeEvent
);
166 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
167 gRMxStateChangeEvent
= NULL
;
172 if( gRMxLockInitialized
)
174 gRMxLockInitialized
= false;
175 DeleteCriticalSection( &gRMxLock
);
178 // Tear down WinSock.
183 //===========================================================================================================================
185 //===========================================================================================================================
189 check( gRMxLockInitialized
);
190 if( gRMxLockInitialized
)
192 EnterCriticalSection( &gRMxLock
);
196 //===========================================================================================================================
198 //===========================================================================================================================
200 void RMxUnlock( void )
202 check( gRMxLockInitialized
);
203 if( gRMxLockInitialized
)
205 LeaveCriticalSection( &gRMxLock
);
211 #pragma mark == Messages ==
214 //===========================================================================================================================
215 // RMxMessageInitialize
216 //===========================================================================================================================
218 void RMxMessageInitialize( RMxMessage
*inMessage
)
223 inMessage
->sendSize
= 0;
224 inMessage
->sendData
= NULL
;
225 inMessage
->recvSize
= 0;
226 inMessage
->recvData
= NULL
;
227 inMessage
->bufferSize
= 0;
228 inMessage
->buffer
= NULL
;
232 //===========================================================================================================================
234 //===========================================================================================================================
236 void RMxMessageRelease( RMxMessage
*inMessage
)
241 // Assert if a malloc'd buffer was used for a small message that could have used the inline message buffer.
243 check( !inMessage
->recvData
||
244 ( inMessage
->recvSize
> sizeof( inMessage
->storage
) ) ||
245 ( inMessage
->recvData
== inMessage
->storage
) );
247 // Free the memory if it was malloc'd (non-null and not the inline message buffer).
249 if( inMessage
->recvData
&& ( inMessage
->recvData
!= inMessage
->storage
) )
251 free( inMessage
->recvData
);
253 inMessage
->recvData
= NULL
;
259 #pragma mark == Sessions ==
262 //===========================================================================================================================
264 //===========================================================================================================================
268 const char * inServer
,
269 RMxSessionFlags inFlags
,
271 RMxMessageCallBack inCallBack
,
273 RMxSessionRef
* outSession
,
274 RMxOpCode inMessageOpCode
,
275 const char * inMessageFormat
,
279 RMxSessionRef session
;
285 dlog( kDebugLevelNotice
, DEBUG_NAME
"opening %s session to %s\n", IsValidSocket( inSock
) ? "server" : "client",
286 inServer
? inServer
: "<local>" );
287 require_action( gRMxState
== kRMxStateRun
, exit
, err
= kStateErr
);
288 require_action( ( inFlags
& ~kRMxSessionOpenValidFlags
) == 0, exit
, err
= kFlagErr
);
290 // Allocate and initialize the object and add it to the session list.
292 session
= (RMxSessionRef
) calloc( 1, sizeof( *session
) );
293 require_action( session
, exit
, err
= kNoMemoryErr
);
295 session
->flags
= inFlags
;
296 session
->sock
= kInvalidSocketRef
;
297 session
->callback
= inCallBack
;
298 RMxMessageInitialize( &session
->message
);
299 session
->message
.context
= inContext
;
300 session
->message
.session
= session
;
301 session
->messageRecvBuffer
= session
->message
.storage
;
302 session
->messageRecvBufferPtr
= session
->messageRecvBuffer
;
303 session
->messageRecvRemaining
= kRMxMessageHeaderSize
;
304 session
->messageRecvHeaderDone
= false;
307 session
->next
= gRMxSessionList
;
308 gRMxSessionList
= session
;
311 session
->closeEvent
= CreateEvent( NULL
, TRUE
, FALSE
, NULL
);
312 err
= translate_errno( session
->closeEvent
, errno_compat(), kNoResourcesErr
);
313 require_noerr( err
, exit
);
315 // Set up the sockets and socket events.
317 if( IsValidSocket( inSock
) )
319 // Server: accepted a connection from a client.
321 session
->flags
|= kRMxSessionFlagsServer
;
322 session
->sock
= inSock
;
323 inSock
= kInvalidSocketRef
;
325 err
= RMxSessionInitServer( session
);
326 require_noerr( err
, exit
);
330 // Client: initiate a connection to the server.
332 err
= RMxSessionInitClient( session
, inServer
);
333 require_noerr( err
, exit
);
336 // Create thread with _beginthreadex() instead of CreateThread() to avoid memory leaks when using static run-time
337 // libraries. See <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createthread.asp>.
338 // Create the thread suspended then resume it so the thread handle and ID are valid before the thread starts running.
342 if( !( inFlags
& kRMxSessionFlagsNoThread
) )
344 session
->thread
= (HANDLE
) _beginthreadex_compat( NULL
, 0, RMxSessionThread
, session
, CREATE_SUSPENDED
, &session
->threadID
);
345 err
= translate_errno( session
->thread
, errno_compat(), kNoResourcesErr
);
346 require_noerr( err
, exit
);
348 result
= ResumeThread( session
->thread
);
349 err
= translate_errno( result
!= (DWORD
) -1, errno_compat(), kNoResourcesErr
);
350 require_noerr( err
, exit
);
353 // Send the optional message. Note: we have to do 2 va_start/va_end because va_copy is not widely supported yet.
355 if( inMessageFormat
)
360 va_start( args1
, inMessageFormat
);
361 va_start( args2
, inMessageFormat
);
362 err
= RMxSessionSendMessageVAList( session
, inMessageOpCode
, kNoErr
, inMessageFormat
, args1
, args2
);
365 require_noerr( err
, exit
);
372 *outSession
= session
;
383 RMxSessionClose( session
, err
);
385 if( IsValidSocket( inSock
) )
387 close_compat( inSock
);
392 //===========================================================================================================================
394 //===========================================================================================================================
396 OSStatus
RMxSessionClose( RMxSessionRef inSession
, OSStatus inReason
)
407 DEBUG_USE_ONLY( inReason
);
411 // Find the session in the list.
416 for( p
= &gRMxSessionList
; *p
; p
= &( *p
)->next
)
418 if( *p
== inSession
)
423 require_action( *p
, exit
, err
= kNotFoundErr
);
425 // If we're being called from the same thread as the session (e.g. message callback is closing the session) then
426 // we must defer the close until the thread is done because the thread is still using the session object.
429 threadID
= GetCurrentThreadId();
430 sameThread
= inSession
->thread
&& ( threadID
== inSession
->threadID
);
431 if( sameThread
&& !( inSession
->flags
& kRMxSessionFlagsThreadDone
) )
433 inSession
->flags
&= ~kRMxSessionFlagsNoClose
;
437 // If the thread we're not being called from the session thread, but the thread has already marked itself as
438 // as done (e.g. session closed from something like a peer disconnect and at the same time the client also
439 // tried to close) then we only want to continue with the close if the thread is not going to close itself.
441 if( !sameThread
&& ( inSession
->flags
& kRMxSessionFlagsThreadDone
) && !( inSession
->flags
& kRMxSessionFlagsNoClose
) )
446 // Signal a close so the thread exits.
448 inSession
->quit
= true;
449 if( inSession
->closeEvent
)
451 ok
= SetEvent( inSession
->closeEvent
);
452 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
459 inSession
->flags
|= kRMxSessionFlagsNoClose
;
461 // Remove the session from the list.
463 *p
= inSession
->next
;
468 // Wait for the thread to exit. Give up after 10 seconds to handle a hung thread.
470 if( inSession
->thread
&& ( threadID
!= inSession
->threadID
) )
472 result
= WaitForSingleObject( inSession
->thread
, 10 * 1000 );
473 check_translated_errno( result
== WAIT_OBJECT_0
, (OSStatus
) GetLastError(), result
);
476 // Release the thread.
478 if( inSession
->thread
)
480 ok
= CloseHandle( inSession
->thread
);
481 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
482 inSession
->thread
= NULL
;
485 // Release the socket event.
487 if( inSession
->sockEvent
)
489 ok
= CloseHandle( inSession
->sockEvent
);
490 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
491 inSession
->sockEvent
= NULL
;
496 if( IsValidSocket( inSession
->sock
) )
498 err
= close_compat( inSession
->sock
);
499 err
= translate_errno( err
== 0, errno_compat(), kUnknownErr
);
501 inSession
->sock
= kInvalidSocketRef
;
504 // Release the close event.
506 if( inSession
->closeEvent
)
508 ok
= CloseHandle( inSession
->closeEvent
);
509 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
510 inSession
->closeEvent
= NULL
;
513 // Release the memory used by the object.
515 RMxMessageRelease( &inSession
->message
);
519 dlog( kDebugLevelNotice
, DEBUG_NAME
"session closed (%d %m)\n", inReason
, inReason
);
529 //===========================================================================================================================
531 //===========================================================================================================================
533 DEBUG_LOCAL
unsigned WINAPI
RMxSessionThread( LPVOID inParam
)
536 RMxSessionRef session
;
539 session
= (RMxSessionRef
) inParam
;
542 // Process messages until the session is told to close or the remote site disconnects.
546 if( session
->quit
|| ( gRMxState
!= kRMxStateRun
) )
548 dlog( kDebugLevelNotice
, DEBUG_NAME
"session state exit (quit=%d, state=%d)\n", session
->quit
, gRMxState
);
553 err
= RMxSessionRecvMessage( session
, INFINITE
);
556 if( session
->callback
)
558 session
->callback( &session
->message
);
560 RMxMessageRelease( &session
->message
);
564 dlog( kDebugLevelNotice
, DEBUG_NAME
"session closing (%d %m)\n", err
, err
);
569 // Tell the callback the session is closing.
571 if( session
->callback
)
573 session
->message
.opcode
= kRMxOpCodeInvalid
;
574 session
->message
.status
= kNoErr
;
575 session
->message
.sendSize
= 0;
576 session
->message
.sendData
= NULL
;
577 session
->message
.recvSize
= 0;
578 session
->message
.recvData
= NULL
;
579 session
->callback( &session
->message
);
582 // Mark the thread as done and close the session (unless asked not to).
585 session
->flags
|= kRMxSessionFlagsThreadDone
;
586 safeToClose
= !( session
->flags
& kRMxSessionFlagsNoClose
);
590 RMxSessionClose( session
, err
);
593 // Call _endthreadex() explicitly instead of just exiting normally to avoid memory leaks when using static run-time
594 // libraries. See <http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dllproc/base/createthread.asp>.
596 _endthreadex_compat( (unsigned) err
);
597 return( (unsigned) err
);
600 //===========================================================================================================================
602 //===========================================================================================================================
604 DEBUG_LOCAL OSStatus
RMxSessionInitServer( RMxSessionRef inSession
)
608 inSession
->sockEvent
= CreateEvent( NULL
, FALSE
, FALSE
, NULL
);
609 err
= translate_errno( inSession
->sockEvent
, errno_compat(), kNoResourcesErr
);
610 require_noerr( err
, exit
);
612 err
= WSAEventSelect( inSession
->sock
, inSession
->sockEvent
, FD_READ
| FD_CLOSE
);
613 err
= translate_errno( err
== 0, errno_compat(), kNoResourcesErr
);
614 require_noerr( err
, exit
);
616 inSession
->waitCount
= 0;
617 inSession
->waitHandles
[ inSession
->waitCount
++ ] = inSession
->sockEvent
;
618 inSession
->waitHandles
[ inSession
->waitCount
++ ] = inSession
->closeEvent
;
619 inSession
->waitHandles
[ inSession
->waitCount
++ ] = gRMxStateChangeEvent
;
620 check( inSession
->waitCount
== sizeof_array( inSession
->waitHandles
) );
626 //===========================================================================================================================
627 // RMxSessionInitClient
628 //===========================================================================================================================
630 DEBUG_LOCAL OSStatus
RMxSessionInitClient( RMxSessionRef inSession
, const char *inServer
)
633 struct addrinfo hints
;
634 struct addrinfo
* addrList
;
635 struct addrinfo
* addr
;
640 // Initiate a connection to the server (if we're the client).
642 memset( &hints
, 0, sizeof( hints
) );
643 hints
.ai_family
= AF_UNSPEC
;
644 hints
.ai_socktype
= SOCK_STREAM
;
645 hints
.ai_protocol
= IPPROTO_TCP
;
647 err
= getaddrinfo( inServer
, kRMxServerPortString
, &hints
, &addrList
);
648 require_noerr( err
, exit
);
650 for( addr
= addrList
; addr
; addr
= addr
->ai_next
)
652 check( addr
->ai_addr
&& ( addr
->ai_addrlen
> 0 ) );
654 if( inSession
->quit
|| ( gRMxState
!= kRMxStateRun
) )
656 dlog( kDebugLevelNotice
, DEBUG_NAME
"session state exit while connecting (quit=%d, state=%d)\n",
657 inSession
->quit
, gRMxState
);
662 // Clean up for any previous iteration that failed. Normal cleanup will occur when closing the session.
664 if( IsValidSocket( inSession
->sock
) )
666 err
= close_compat( inSession
->sock
);
667 check_translated_errno( err
== 0, errno_compat(), kUnknownErr
);
668 inSession
->sock
= kInvalidSocketRef
;
671 if( inSession
->sockEvent
)
673 ok
= CloseHandle( inSession
->sockEvent
);
674 check_translated_errno( ok
, errno_compat(), kUnknownErr
);
675 inSession
->sockEvent
= NULL
;
678 // Set up the socket and try to connect.
680 dlog( kDebugLevelTrace
, DEBUG_NAME
"connecting %s socket to %s/%##a\n",
681 ( addr
->ai_family
== AF_INET
) ? "AF_INET" : ( addr
->ai_family
== AF_INET6
) ? "AF_INET6" : "<unknown>",
682 inServer
? inServer
: "<local>", addr
->ai_addr
);
684 inSession
->sock
= socket( addr
->ai_family
, addr
->ai_socktype
, addr
->ai_protocol
);
685 err
= translate_errno( IsValidSocket( inSession
->sock
), errno_compat(), kNoResourcesErr
);
688 dlog( kDebugLevelNotice
, DEBUG_NAME
"%s socket not supported...skipping (%d %m)\n",
689 ( addr
->ai_family
== AF_INET
) ? "AF_INET" : ( addr
->ai_family
== AF_INET6
) ? "AF_INET6" : "<unknown>",
694 inSession
->sockEvent
= CreateEvent( NULL
, FALSE
, FALSE
, NULL
);
695 err
= translate_errno( inSession
->sockEvent
, errno_compat(), kNoResourcesErr
);
696 require_noerr( err
, exit
);
698 err
= WSAEventSelect( inSession
->sock
, inSession
->sockEvent
, FD_READ
| FD_CONNECT
| FD_CLOSE
);
699 err
= translate_errno( err
== 0, errno_compat(), kNoResourcesErr
);
700 require_noerr( err
, exit
);
702 inSession
->waitCount
= 0;
703 inSession
->waitHandles
[ inSession
->waitCount
++ ] = inSession
->sockEvent
;
704 inSession
->waitHandles
[ inSession
->waitCount
++ ] = inSession
->closeEvent
;
705 inSession
->waitHandles
[ inSession
->waitCount
++ ] = gRMxStateChangeEvent
;
706 check( inSession
->waitCount
== sizeof_array( inSession
->waitHandles
) );
708 err
= RMxSessionConnect( inSession
, addr
->ai_addr
, addr
->ai_addrlen
);
714 require_action( addr
, exit
, err
= kConnectionErr
);
719 freeaddrinfo( addrList
);
724 //===========================================================================================================================
726 //===========================================================================================================================
728 DEBUG_LOCAL OSStatus
RMxSessionConnect( RMxSessionRef inSession
, const struct sockaddr
*inAddr
, size_t inAddrSize
)
734 check( inSession
->sock
);
735 check( inSession
->waitCount
> 0 );
736 check( inAddr
&& ( inAddrSize
> 0 ) );
738 // Start the connection process. This returns immediately. If the error is 0, it means the connection has
739 // successfully completed (usually only if the host is local). Otherwise, it returns an error to indicate
740 // that the connection process has started and we must use wait for it to complete.
742 err
= connect( inSession
->sock
, inAddr
, (int) inAddrSize
);
743 if( err
== 0 ) goto exit
;
745 // Wait for the connection to complete, timeout, or be canceled.
747 result
= WaitForMultipleObjects( inSession
->waitCount
, inSession
->waitHandles
, FALSE
, kRMxClientTimeout
);
748 if( result
== WAIT_OBJECT_0
) // Socket Event
750 WSANETWORKEVENTS events
;
752 // Check the result of the FD_CONNECT event to see if the connection was successful or not.
754 err
= WSAEnumNetworkEvents( inSession
->sock
, NULL
, &events
);
755 require_noerr( err
, exit
);
756 require_action( events
.lNetworkEvents
& FD_CONNECT
, exit
, err
= kSelectorErr
);
757 err
= events
.iErrorCode
[ FD_CONNECT_BIT
];
759 else if( result
== WAIT_TIMEOUT
)
772 //===========================================================================================================================
773 // RMxSessionSendMessage
775 // Warning: Assumes the RMx lock is held.
776 //===========================================================================================================================
778 OSStatus
RMxSessionSendMessage( RMxSessionRef inSession
, RMxOpCode inOpCode
, OSStatus inStatus
, const char *inFormat
, ... )
784 // Note: 2 va_list's need to be provided as the va_list needs to be processed twice (once to preflight, once for
785 // real) and this is illegal without C99 va_copy, but some environments do not yet support va_copy (e.g. Visual C++).
787 va_start( args1
, inFormat
);
788 va_start( args2
, inFormat
);
789 err
= RMxSessionSendMessageVAList( inSession
, inOpCode
, inStatus
, inFormat
, args1
, args2
);
792 require_noerr( err
, exit
);
798 //===========================================================================================================================
799 // RMxSessionSendMessageVAList
801 // Warning: Assumes the RMx lock is held.
802 //===========================================================================================================================
805 RMxSessionSendMessageVAList(
806 RMxSessionRef inSession
,
809 const char * inFormat
,
815 uint8_t * bufferStorage
;
822 bufferStorage
= NULL
;
824 check( IsValidSocket( inSession
->sock
) );
827 // Calculate the size of the data section of the message and set up the buffer for the entire message.
828 // If the entire message will fit in the inline message buffer, use it. Otherwise, allocate a buffer for it.
830 err
= RMxPackedSizeVAList( &bodySize
, inFormat
, inArgs1
);
831 require_noerr( err
, exit
);
833 size
= kRMxMessageHeaderSize
+ bodySize
;
834 if( size
<= sizeof( msg
.storage
) )
836 buffer
= msg
.storage
;
840 bufferStorage
= (uint8_t *) malloc( size
);
841 require_action( bufferStorage
, exit
, err
= kNoMemoryErr
);
842 buffer
= bufferStorage
;
845 // Build the message header.
847 err
= RMxPack( buffer
, kRMxMessageHeaderSize
, &headerSize
, "wwwwww",
848 kRMxSignatureVersion1
, // signature
850 kRMxFlagsNone
, // flags
853 (uint32_t) bodySize
); // size
854 require_noerr( err
, exit
);
855 check( headerSize
== kRMxMessageHeaderSize
);
857 // Build the message body.
859 err
= RMxPackVAList( buffer
+ kRMxMessageHeaderSize
, size
- kRMxMessageHeaderSize
, &size
, inFormat
, inArgs2
);
860 require_noerr( err
, exit
);
861 check( size
== bodySize
);
863 // Send the entire message.
865 size
= headerSize
+ size
;
866 n
= send( inSession
->sock
, (const char *) buffer
, (int) size
, 0 );
867 err
= translate_errno( n
== (int) size
, errno_compat(), kWriteErr
);
868 require_noerr( err
, exit
);
873 free( bufferStorage
);
878 //===========================================================================================================================
879 // RMxSessionRecvMessage
881 // Note: This routine maintains state within the session data structure and can resume partial message reception.
882 //===========================================================================================================================
884 OSStatus
RMxSessionRecvMessage( RMxSessionRef inSession
, DWORD inTimeout
)
892 if( inSession
->quit
|| ( gRMxState
!= kRMxStateRun
) )
894 dlog( kDebugLevelNotice
, DEBUG_NAME
"session recv state exit (quit=%d, state=%d)\n", inSession
->quit
, gRMxState
);
899 // Wait for data to become available or another event to occur.
901 result
= WaitForMultipleObjects( inSession
->waitCount
, inSession
->waitHandles
, FALSE
, inTimeout
);
902 if( result
== WAIT_OBJECT_0
) // Socket Event (i.e. data available to read)
904 n
= recv( inSession
->sock
, (char *) inSession
->messageRecvBufferPtr
, inSession
->messageRecvRemaining
, 0 );
907 inSession
->messageRecvBufferPtr
+= n
;
908 inSession
->messageRecvRemaining
-= n
;
909 if( inSession
->messageRecvRemaining
== 0 )
911 // Complete chunk ready. If we haven't read the header yet, it's the header. Otherwise, it's the data.
913 if( !inSession
->messageRecvHeaderDone
)
915 // Parse the buffer into the message header structure and mark the header as complete.
917 err
= RMxUnpack( inSession
->messageRecvBuffer
, kRMxMessageHeaderSize
,
919 &inSession
->message
.signature
,
920 &inSession
->message
.opcode
,
921 &inSession
->message
.flags
,
922 &inSession
->message
.xid
,
923 &inSession
->message
.status
,
924 &inSession
->message
.recvSize
);
925 require_noerr( err
, exit
);
926 require_action( inSession
->message
.signature
== kRMxSignatureVersion1
, exit
, err
= kMismatchErr
);
927 require_action( inSession
->message
.opcode
!= kRMxOpCodeInvalid
, exit
, err
= kMismatchErr
);
928 inSession
->messageRecvHeaderDone
= true;
930 // Set up to read the data section (if any). Use the inline message buffer if the data will fit.
932 if( inSession
->message
.recvSize
> 0 )
934 if( inSession
->message
.recvSize
<= sizeof( inSession
->message
.storage
) )
936 inSession
->message
.recvData
= inSession
->message
.storage
;
940 inSession
->message
.recvData
= (uint8_t *) malloc( inSession
->message
.recvSize
);
941 require_action( inSession
->message
.recvData
, exit
, err
= kNoMemoryErr
);
943 inSession
->messageRecvBufferPtr
= inSession
->message
.recvData
;
944 inSession
->messageRecvRemaining
= (int) inSession
->message
.recvSize
;
948 if( inSession
->messageRecvHeaderDone
&& ( inSession
->messageRecvRemaining
== 0 ) )
950 // Complete message ready. Reset state for next message. Exit to allow message to be processed.
952 inSession
->messageRecvBufferPtr
= inSession
->messageRecvBuffer
;
953 inSession
->messageRecvRemaining
= kRMxMessageHeaderSize
;
954 inSession
->messageRecvHeaderDone
= false;
960 err
= errno_compat();
961 dlog( kDebugLevelNotice
, DEBUG_NAME
"session recv peer disconnected (%d/%d %m)\n", n
, err
, err
);
962 err
= kConnectionErr
;
966 else if( result
== ( WAIT_OBJECT_0
+ 1 ) ) // Close Event
968 dlog( kDebugLevelNotice
, DEBUG_NAME
"session recv close signaled\n" );
972 else if( result
== ( WAIT_OBJECT_0
+ 2 ) ) // State Change Event
974 dlog( kDebugLevelNotice
, DEBUG_NAME
"session recv state change (%d)\n", gRMxState
);
978 else if( result
== WAIT_TIMEOUT
) // Timeout
980 dlog( kDebugLevelNotice
, DEBUG_NAME
"session recv timeout\n" );
986 err
= errno_compat();
987 dlog( kDebugLevelAlert
, DEBUG_NAME
"session recv message wait error: 0x%08X, %d (%m)\n", result
, err
, err
);
988 err
= (OSStatus
) result
;
1000 #pragma mark == Utilities ==
1003 //===========================================================================================================================
1005 //===========================================================================================================================
1009 uint32_t inClientCurrentVersion
, uint32_t inClientOldestClientVersion
, uint32_t inClientOldestServerVersion
,
1010 uint32_t inServerCurrentVersion
, uint32_t inServerOldestClientVersion
, uint32_t inServerOldestServerVersion
)
1013 const char * message
;
1015 DEBUG_USE_ONLY( inClientOldestClientVersion
);
1016 DEBUG_USE_ONLY( inServerOldestServerVersion
);
1017 DEBUG_USE_ONLY( message
);
1019 // Determine if the version information on both sides is compatible.
1021 if( inClientCurrentVersion
== inServerCurrentVersion
)
1023 message
= "versions exactly match";
1026 else if( inClientCurrentVersion
> inServerCurrentVersion
)
1028 if( inClientOldestServerVersion
<= inServerCurrentVersion
)
1030 message
= "client newer, but compatible";
1035 message
= "server too old for client";
1036 err
= kIncompatibleErr
;
1041 if( inServerOldestClientVersion
<= inClientCurrentVersion
)
1043 message
= "server newer, but compatible";
1048 message
= "client too old for server";
1049 err
= kIncompatibleErr
;
1052 dlog( kDebugLevelNotice
, DEBUG_NAME
"%s (client=%v/%v/%v vs server=%v/%v/%v)\n", message
,
1053 inClientCurrentVersion
, inClientOldestClientVersion
, inClientOldestServerVersion
,
1054 inServerCurrentVersion
, inServerOldestClientVersion
, inServerOldestServerVersion
);
1059 //===========================================================================================================================
1061 //===========================================================================================================================
1063 OSStatus
RMxPackedSize( size_t *outSize
, const char *inFormat
, ... )
1068 va_start( args
, inFormat
);
1069 err
= RMxPackedSizeVAList( outSize
, inFormat
, args
);
1075 //===========================================================================================================================
1076 // RMxPackedSizeVAList
1077 //===========================================================================================================================
1079 OSStatus
RMxPackedSizeVAList( size_t *outSize
, const char *inFormat
, va_list inArgs
)
1084 const uint8_t * src
;
1088 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1092 // Loop thru each character in the format string, decode it, and add the size required to pack it.
1094 for( c
= *inFormat
; c
!= '\0'; c
= *( ++inFormat
) )
1098 case 'b': // Byte (8-bit)
1100 va_arg( inArgs
, unsigned int );
1104 case 'h': // Half-word (16-bit)
1106 va_arg( inArgs
, unsigned int );
1110 case 'w': // Word (32-bit)
1112 va_arg( inArgs
, unsigned int );
1116 case 's': // UTF-8 String, null terminated
1118 src
= va_arg( inArgs
, const uint8_t * );
1122 while( *p
++ != 0 ) {}
1123 size
+= ( p
- src
);
1126 case 'n': // N bytes of raw data; 1st arg is size, 2nd arg is ptr; stored with 32-bit length prefix.
1128 tempU32
= (uint32_t) va_arg( inArgs
, unsigned int );
1129 src
= va_arg( inArgs
, const uint8_t * );
1130 check( src
|| ( tempU32
== 0 ) );
1132 size
+= ( 4 + tempU32
);
1135 case ' ': // Ignore spaces (they're just for format string readability).
1138 default: // Unknown format specifier
1140 err
= kUnsupportedErr
;
1157 //===========================================================================================================================
1159 //===========================================================================================================================
1161 OSStatus
RMxPack( void *inBuffer
, size_t inMaxSize
, size_t *outSize
, const char *inFormat
, ... )
1166 va_start( args
, inFormat
);
1167 err
= RMxPackVAList( inBuffer
, inMaxSize
, outSize
, inFormat
, args
);
1173 //===========================================================================================================================
1175 //===========================================================================================================================
1177 OSStatus
RMxPackVAList( void *inBuffer
, size_t inMaxSize
, size_t *outSize
, const char *inFormat
, va_list inArgs
)
1181 const uint8_t * src
;
1190 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1192 dst
= (uint8_t *) inBuffer
;
1193 end
= dst
+ inMaxSize
;
1195 // Loop thru each character in the format string, decode it, and pack the data appropriately.
1197 for( c
= *inFormat
; c
!= '\0'; c
= *( ++inFormat
) )
1201 case 'b': // Byte (8-bit)
1203 check( ( end
- dst
) >= 1 );
1204 tempU8
= (uint8_t) va_arg( inArgs
, unsigned int );
1208 case 'h': // Half-word (16-bit)
1210 check( ( end
- dst
) >= 2 );
1211 tempU16
= (uint16_t) va_arg( inArgs
, unsigned int );
1212 *dst
++ = (uint8_t)( ( tempU16
>> 8 ) & 0xFF );
1213 *dst
++ = (uint8_t)( tempU16
& 0xFF );
1216 case 'w': // Word (32-bit)
1218 check( ( end
- dst
) >= 4 );
1219 tempU32
= (uint32_t) va_arg( inArgs
, unsigned int );
1220 *dst
++ = (uint8_t)( ( tempU32
>> 24 ) & 0xFF );
1221 *dst
++ = (uint8_t)( ( tempU32
>> 16 ) & 0xFF );
1222 *dst
++ = (uint8_t)( ( tempU32
>> 8 ) & 0xFF );
1223 *dst
++ = (uint8_t)( tempU32
& 0xFF );
1226 case 's': // UTF-8 String, null terminated
1228 src
= va_arg( inArgs
, const uint8_t * );
1232 while( *p
++ != 0 ) {}
1233 size
= (size_t)( p
- src
);
1234 check( ( end
- dst
) >= (ptrdiff_t) size
);
1242 case 'n': // N bytes of raw data; 1st arg is size, 2nd arg is ptr; stored with 32-bit length prefix.
1244 tempU32
= (uint32_t) va_arg( inArgs
, unsigned int );
1245 check( ( end
- dst
) >= (ptrdiff_t)( 4 + tempU32
) );
1247 src
= va_arg( inArgs
, const uint8_t * );
1248 check( src
|| ( tempU32
== 0 ) );
1250 *dst
++ = (uint8_t)( ( tempU32
>> 24 ) & 0xFF );
1251 *dst
++ = (uint8_t)( ( tempU32
>> 16 ) & 0xFF );
1252 *dst
++ = (uint8_t)( ( tempU32
>> 8 ) & 0xFF );
1253 *dst
++ = (uint8_t)( tempU32
& 0xFF );
1254 while( tempU32
-- > 0 )
1260 case ' ': // Ignore spaces (they're just for format string readability).
1263 default: // Unknown format specifier
1265 err
= kUnsupportedErr
;
1274 *outSize
= (size_t)( dst
- ( (uint8_t *) inBuffer
) );
1282 //===========================================================================================================================
1284 //===========================================================================================================================
1286 OSStatus
RMxUnpack( const void *inData
, size_t inSize
, const char *inFormat
, ... )
1291 va_start( args
, inFormat
);
1292 err
= RMxUnpackVAList( inData
, inSize
, inFormat
, args
);
1298 //===========================================================================================================================
1300 //===========================================================================================================================
1302 OSStatus
RMxUnpackVAList( const void *inData
, size_t inSize
, const char *inFormat
, va_list inArgs
)
1306 const uint8_t * src
;
1307 const uint8_t * end
;
1315 const uint8_t ** ptrArg
;
1318 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1320 src
= (const uint8_t *) inData
;
1323 // Loop thru each character in the format string, decode it, and unpack the data appropriately.
1325 for( c
= *inFormat
; c
!= '\0'; c
= *( ++inFormat
) )
1329 case 'b': // Byte (8-bit)
1331 require_action( ( end
- src
) >= 1, exit
, err
= kSizeErr
);
1332 b
= va_arg( inArgs
, uint8_t * );
1340 case 'h': // Half-word (16-bit)
1342 require_action( ( end
- src
) >= 2, exit
, err
= kSizeErr
);
1343 tempU16
= (uint16_t)( *src
++ << 8 );
1344 tempU16
|= (uint16_t)( *src
++ );
1345 h
= va_arg( inArgs
, uint16_t * );
1352 case 'w': // Word (32-bit)
1354 require_action( ( end
- src
) >= 4, exit
, err
= kSizeErr
);
1355 tempU32
= (uint32_t)( *src
++ << 24 );
1356 tempU32
|= (uint32_t)( *src
++ << 16 );
1357 tempU32
|= (uint32_t)( *src
++ << 8 );
1358 tempU32
|= (uint32_t)( *src
++ );
1359 w
= va_arg( inArgs
, uint32_t * );
1366 case 's': // UTF-8 String, null terminated; 1st arg=ptr to ptr, 2nd arg=ptr to size, excluding null terminator.
1369 while( ( ( end
- p
) > 0 ) && ( *p
!= 0 ) )
1373 require_action( ( end
- p
) > 0, exit
, err
= kSizeErr
);
1374 size
= (size_t)( p
- src
);
1376 ptrArg
= va_arg( inArgs
, const uint8_t ** );
1382 sizeArg
= va_arg( inArgs
, size_t * );
1391 case 'n': // N bytes of raw data; 1st arg is ptr to ptr, 2nd arg is ptr to size.
1393 require_action( ( end
- src
) >= 4, exit
, err
= kSizeErr
);
1394 tempU32
= (uint32_t)( *src
++ << 24 );
1395 tempU32
|= (uint32_t)( *src
++ << 16 );
1396 tempU32
|= (uint32_t)( *src
++ << 8 );
1397 tempU32
|= (uint32_t)( *src
++ );
1398 require_action( ( end
- src
) >= (ptrdiff_t) tempU32
, exit
, err
= kSizeErr
);
1399 size
= (size_t) tempU32
;
1401 ptrArg
= va_arg( inArgs
, const uint8_t ** );
1407 sizeArg
= va_arg( inArgs
, size_t * );
1416 case ' ': // Ignore spaces (they're just for format string readability).
1419 default: // Unknown format specifier
1421 err
= kUnsupportedErr
;
1435 //===========================================================================================================================
1436 // RMxPackUnpackTest
1437 //===========================================================================================================================
1439 OSStatus
RMxPackUnpackTest( void );
1441 OSStatus
RMxPackUnpackTest( void )
1443 static const uint8_t data
[] =
1447 0x11, 0x22, 0x33, 0x44,
1448 0x68, 0x65, 0x6C, 0x6C, 0x6F, 0x20, 0x70, 0x65, 0x6F, 0x70, 0x6C, 0x65, 0x00, // hello people\0
1449 0x00, 0x00, 0x00, 0x05, 0x74, 0x65, 0x73, 0x74, 0x73 // tests
1452 uint8_t buffer
[ 128 ];
1462 check_compile_time_code( sizeof( data
) >= 29 );
1467 printf( "\nsimple API test\n" );
1469 err
= RMxPackedSize( &size
, "bhwsn ", 0xAA, 0xBBCC, 0x11223344, "hello people", 5, "tests" );
1470 require_noerr( err
, exit
);
1471 require_action( size
== 29, exit
, err
= kSizeErr
);
1473 err
= RMxPack( buffer
, 29, &size
, " bhwsn", 0xAA, 0xBBCC, 0x11223344, "hello people", 5, "tests" );
1474 require_noerr( err
, exit
);
1475 require_action( size
== 29, exit
, err
= kSizeErr
);
1476 require_action( memcmp( buffer
, data
, size
) == 0, exit
, err
= kMismatchErr
);
1478 err
= RMxUnpack( data
, sizeof( data
), "bhw sn", &b
, &h
, &w
, &s
, &sSize
, &d
, &dSize
);
1479 require_noerr( err
, exit
);
1480 require_action( b
== 0xAA, exit
, err
= kMismatchErr
);
1481 require_action( h
== 0xBBCC, exit
, err
= kMismatchErr
);
1482 require_action( w
== 0x11223344, exit
, err
= kMismatchErr
);
1483 require_action( sSize
== 12, exit
, err
= kSizeErr
);
1484 require_action( strcmp( s
, "hello people" ) == 0, exit
, err
= kMismatchErr
);
1485 require_action( dSize
== 5, exit
, err
= kSizeErr
);
1486 require_action( memcmp( d
, "tests", 5 ) == 0, exit
, err
= kMismatchErr
);
1488 printf( "\nsimple API test done\n\n" );
1492 printf( "\n\nALL TESTS PASSED\n\n" );