]> git.saurik.com Git - apple/mdnsresponder.git/blob - mDNSWindows/RMxCommon.c
mDNSResponder-66.3.tar.gz
[apple/mdnsresponder.git] / mDNSWindows / RMxCommon.c
1 /*
2 * Copyright (c) 2003-2004 Apple Computer, Inc. All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * Copyright (c) 1999-2003 Apple Computer, Inc. All Rights Reserved.
7 *
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
13 * file.
14 *
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.
22 *
23 * @APPLE_LICENSE_HEADER_END@
24
25 Change History (most recent first):
26
27 $Log: RMxCommon.c,v $
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.
31
32 Revision 1.1 2004/01/30 02:35:13 bradley
33 Rendezvous Message Exchange implementation for DNS-SD IPC on Windows.
34
35 */
36
37 #include <stdlib.h>
38 #include <stdio.h>
39 #include <string.h>
40
41 #include "CommonServices.h"
42
43 #include <process.h>
44
45 #include "RMxCommon.h"
46
47 #ifdef __cplusplus
48 extern "C" {
49 #endif
50
51 //===========================================================================================================================
52 // Constants
53 //===========================================================================================================================
54
55 #define DEBUG_NAME "[RMxCommon] "
56
57 #define kRMxSessionOpenValidFlags ( kRMxSessionFlagsNoThread | kRMxSessionFlagsNoClose )
58
59 #if 0
60 #pragma mark == Prototypes ==
61 #endif
62
63 //===========================================================================================================================
64 // Prototypes
65 //===========================================================================================================================
66
67 // Session
68
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 );
73 DEBUG_LOCAL OSStatus
74 RMxSessionSendMessageVAList(
75 RMxSessionRef inSession,
76 RMxOpCode inOpCode,
77 OSStatus inStatus,
78 const char * inFormat,
79 va_list inArgs1,
80 va_list inArgs2 );
81
82 #if 0
83 #pragma mark == Globals ==
84 #endif
85
86 //===========================================================================================================================
87 // Globals
88 //===========================================================================================================================
89
90 // General
91
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;
97
98 #if 0
99 #pragma mark -
100 #pragma mark == General ==
101 #endif
102
103 //===========================================================================================================================
104 // RMxInitialize
105 //===========================================================================================================================
106
107 OSStatus RMxInitialize( void )
108 {
109 OSStatus err;
110 WSADATA wsaData;
111
112 // Set up WinSock.
113
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 );
117
118 // Set up the global locked used to protect things like the session list.
119
120 InitializeCriticalSection( &gRMxLock );
121 gRMxLockInitialized = true;
122
123 // Set up the state and state changed event. A manual-reset event is used so all threads wake up when it is signaled.
124
125 gRMxState = kRMxStateInvalid;
126 gRMxStateChangeEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
127 err = translate_errno( gRMxStateChangeEvent, errno_compat(), kNoResourcesErr );
128 require_noerr( err, exit );
129
130 exit:
131 return( err );
132 }
133
134 //===========================================================================================================================
135 // RMxFinalize
136 //===========================================================================================================================
137
138 void RMxFinalize( void )
139 {
140 BOOL ok;
141 OSStatus err;
142
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.
145
146 gRMxState = kRMxStateStop;
147 if( gRMxStateChangeEvent )
148 {
149 ok = SetEvent( gRMxStateChangeEvent );
150 check_translated_errno( ok, errno_compat(), kUnknownErr );
151 }
152
153 // Close any open sessions.
154
155 while( gRMxSessionList )
156 {
157 err = RMxSessionClose( gRMxSessionList, kEndingErr );
158 check( ( err == kNoErr ) || ( err == kNotFoundErr ) );
159 }
160
161 // Release the state changed event.
162
163 if( gRMxStateChangeEvent )
164 {
165 ok = CloseHandle( gRMxStateChangeEvent );
166 check_translated_errno( ok, errno_compat(), kUnknownErr );
167 gRMxStateChangeEvent = NULL;
168 }
169
170 // Release the lock.
171
172 if( gRMxLockInitialized )
173 {
174 gRMxLockInitialized = false;
175 DeleteCriticalSection( &gRMxLock );
176 }
177
178 // Tear down WinSock.
179
180 WSACleanup();
181 }
182
183 //===========================================================================================================================
184 // RMxLock
185 //===========================================================================================================================
186
187 void RMxLock( void )
188 {
189 check( gRMxLockInitialized );
190 if( gRMxLockInitialized )
191 {
192 EnterCriticalSection( &gRMxLock );
193 }
194 }
195
196 //===========================================================================================================================
197 // RMxUnlock
198 //===========================================================================================================================
199
200 void RMxUnlock( void )
201 {
202 check( gRMxLockInitialized );
203 if( gRMxLockInitialized )
204 {
205 LeaveCriticalSection( &gRMxLock );
206 }
207 }
208
209 #if 0
210 #pragma mark -
211 #pragma mark == Messages ==
212 #endif
213
214 //===========================================================================================================================
215 // RMxMessageInitialize
216 //===========================================================================================================================
217
218 void RMxMessageInitialize( RMxMessage *inMessage )
219 {
220 check( inMessage );
221 if( inMessage )
222 {
223 inMessage->sendSize = 0;
224 inMessage->sendData = NULL;
225 inMessage->recvSize = 0;
226 inMessage->recvData = NULL;
227 inMessage->bufferSize = 0;
228 inMessage->buffer = NULL;
229 }
230 }
231
232 //===========================================================================================================================
233 // RMxMessageRelease
234 //===========================================================================================================================
235
236 void RMxMessageRelease( RMxMessage *inMessage )
237 {
238 check( inMessage );
239 if( inMessage )
240 {
241 // Assert if a malloc'd buffer was used for a small message that could have used the inline message buffer.
242
243 check( !inMessage->recvData ||
244 ( inMessage->recvSize > sizeof( inMessage->storage ) ) ||
245 ( inMessage->recvData == inMessage->storage ) );
246
247 // Free the memory if it was malloc'd (non-null and not the inline message buffer).
248
249 if( inMessage->recvData && ( inMessage->recvData != inMessage->storage ) )
250 {
251 free( inMessage->recvData );
252 }
253 inMessage->recvData = NULL;
254 }
255 }
256
257 #if 0
258 #pragma mark -
259 #pragma mark == Sessions ==
260 #endif
261
262 //===========================================================================================================================
263 // RMxSessionOpen
264 //===========================================================================================================================
265
266 OSStatus
267 RMxSessionOpen(
268 const char * inServer,
269 RMxSessionFlags inFlags,
270 SocketRef inSock,
271 RMxMessageCallBack inCallBack,
272 void * inContext,
273 RMxSessionRef * outSession,
274 RMxOpCode inMessageOpCode,
275 const char * inMessageFormat,
276 ... )
277 {
278 OSStatus err;
279 RMxSessionRef session;
280 DWORD result;
281 bool locked;
282
283 session = NULL;
284 locked = false;
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 );
289
290 // Allocate and initialize the object and add it to the session list.
291
292 session = (RMxSessionRef) calloc( 1, sizeof( *session ) );
293 require_action( session, exit, err = kNoMemoryErr );
294
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;
305
306 RMxLock();
307 session->next = gRMxSessionList;
308 gRMxSessionList = session;
309 RMxUnlock();
310
311 session->closeEvent = CreateEvent( NULL, TRUE, FALSE, NULL );
312 err = translate_errno( session->closeEvent, errno_compat(), kNoResourcesErr );
313 require_noerr( err, exit );
314
315 // Set up the sockets and socket events.
316
317 if( IsValidSocket( inSock ) )
318 {
319 // Server: accepted a connection from a client.
320
321 session->flags |= kRMxSessionFlagsServer;
322 session->sock = inSock;
323 inSock = kInvalidSocketRef;
324
325 err = RMxSessionInitServer( session );
326 require_noerr( err, exit );
327 }
328 else
329 {
330 // Client: initiate a connection to the server.
331
332 err = RMxSessionInitClient( session, inServer );
333 require_noerr( err, exit );
334 }
335
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.
339
340 RMxLock();
341 locked = true;
342 if( !( inFlags & kRMxSessionFlagsNoThread ) )
343 {
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 );
347
348 result = ResumeThread( session->thread );
349 err = translate_errno( result != (DWORD) -1, errno_compat(), kNoResourcesErr );
350 require_noerr( err, exit );
351 }
352
353 // Send the optional message. Note: we have to do 2 va_start/va_end because va_copy is not widely supported yet.
354
355 if( inMessageFormat )
356 {
357 va_list args1;
358 va_list args2;
359
360 va_start( args1, inMessageFormat );
361 va_start( args2, inMessageFormat );
362 err = RMxSessionSendMessageVAList( session, inMessageOpCode, kNoErr, inMessageFormat, args1, args2 );
363 va_end( args1 );
364 va_end( args2 );
365 require_noerr( err, exit );
366 }
367
368 // Success!
369
370 if( outSession )
371 {
372 *outSession = session;
373 }
374 session = NULL;
375
376 exit:
377 if( locked )
378 {
379 RMxUnlock();
380 }
381 if( session )
382 {
383 RMxSessionClose( session, err );
384 }
385 if( IsValidSocket( inSock ) )
386 {
387 close_compat( inSock );
388 }
389 return( err );
390 }
391
392 //===========================================================================================================================
393 // RMxSessionClose
394 //===========================================================================================================================
395
396 OSStatus RMxSessionClose( RMxSessionRef inSession, OSStatus inReason )
397 {
398 OSStatus err;
399 bool locked;
400 RMxSessionRef * p;
401 bool sameThread;
402 bool deferClose;
403 BOOL ok;
404 DWORD threadID;
405 DWORD result;
406
407 DEBUG_USE_ONLY( inReason );
408
409 check( inSession );
410
411 // Find the session in the list.
412
413 RMxLock();
414 locked = true;
415
416 for( p = &gRMxSessionList; *p; p = &( *p )->next )
417 {
418 if( *p == inSession )
419 {
420 break;
421 }
422 }
423 require_action( *p, exit, err = kNotFoundErr );
424
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.
427
428 deferClose = false;
429 threadID = GetCurrentThreadId();
430 sameThread = inSession->thread && ( threadID == inSession->threadID );
431 if( sameThread && !( inSession->flags & kRMxSessionFlagsThreadDone ) )
432 {
433 inSession->flags &= ~kRMxSessionFlagsNoClose;
434 deferClose = true;
435 }
436
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.
440
441 if( !sameThread && ( inSession->flags & kRMxSessionFlagsThreadDone ) && !( inSession->flags & kRMxSessionFlagsNoClose ) )
442 {
443 deferClose = true;
444 }
445
446 // Signal a close so the thread exits.
447
448 inSession->quit = true;
449 if( inSession->closeEvent )
450 {
451 ok = SetEvent( inSession->closeEvent );
452 check_translated_errno( ok, errno_compat(), kUnknownErr );
453 }
454 if( deferClose )
455 {
456 err = kNoErr;
457 goto exit;
458 }
459 inSession->flags |= kRMxSessionFlagsNoClose;
460
461 // Remove the session from the list.
462
463 *p = inSession->next;
464
465 RMxUnlock();
466 locked = false;
467
468 // Wait for the thread to exit. Give up after 10 seconds to handle a hung thread.
469
470 if( inSession->thread && ( threadID != inSession->threadID ) )
471 {
472 result = WaitForSingleObject( inSession->thread, 10 * 1000 );
473 check_translated_errno( result == WAIT_OBJECT_0, (OSStatus) GetLastError(), result );
474 }
475
476 // Release the thread.
477
478 if( inSession->thread )
479 {
480 ok = CloseHandle( inSession->thread );
481 check_translated_errno( ok, errno_compat(), kUnknownErr );
482 inSession->thread = NULL;
483 }
484
485 // Release the socket event.
486
487 if( inSession->sockEvent )
488 {
489 ok = CloseHandle( inSession->sockEvent );
490 check_translated_errno( ok, errno_compat(), kUnknownErr );
491 inSession->sockEvent = NULL;
492 }
493
494 // Close the socket.
495
496 if( IsValidSocket( inSession->sock ) )
497 {
498 err = close_compat( inSession->sock );
499 err = translate_errno( err == 0, errno_compat(), kUnknownErr );
500 check_noerr( err );
501 inSession->sock = kInvalidSocketRef;
502 }
503
504 // Release the close event.
505
506 if( inSession->closeEvent )
507 {
508 ok = CloseHandle( inSession->closeEvent );
509 check_translated_errno( ok, errno_compat(), kUnknownErr );
510 inSession->closeEvent = NULL;
511 }
512
513 // Release the memory used by the object.
514
515 RMxMessageRelease( &inSession->message );
516 free( inSession );
517 err = kNoErr;
518
519 dlog( kDebugLevelNotice, DEBUG_NAME "session closed (%d %m)\n", inReason, inReason );
520
521 exit:
522 if( locked )
523 {
524 RMxUnlock();
525 }
526 return( err );
527 }
528
529 //===========================================================================================================================
530 // RMxSessionThread
531 //===========================================================================================================================
532
533 DEBUG_LOCAL unsigned WINAPI RMxSessionThread( LPVOID inParam )
534 {
535 OSStatus err;
536 RMxSessionRef session;
537 bool safeToClose;
538
539 session = (RMxSessionRef) inParam;
540 check( session );
541
542 // Process messages until the session is told to close or the remote site disconnects.
543
544 for( ;; )
545 {
546 if( session->quit || ( gRMxState != kRMxStateRun ) )
547 {
548 dlog( kDebugLevelNotice, DEBUG_NAME "session state exit (quit=%d, state=%d)\n", session->quit, gRMxState );
549 err = kEndingErr;
550 break;
551 }
552
553 err = RMxSessionRecvMessage( session, INFINITE );
554 if( err == kNoErr )
555 {
556 if( session->callback )
557 {
558 session->callback( &session->message );
559 }
560 RMxMessageRelease( &session->message );
561 }
562 else
563 {
564 dlog( kDebugLevelNotice, DEBUG_NAME "session closing (%d %m)\n", err, err );
565 break;
566 }
567 }
568
569 // Tell the callback the session is closing.
570
571 if( session->callback )
572 {
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 );
580 }
581
582 // Mark the thread as done and close the session (unless asked not to).
583
584 RMxLock();
585 session->flags |= kRMxSessionFlagsThreadDone;
586 safeToClose = !( session->flags & kRMxSessionFlagsNoClose );
587 RMxUnlock();
588 if( safeToClose )
589 {
590 RMxSessionClose( session, err );
591 }
592
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>.
595
596 _endthreadex_compat( (unsigned) err );
597 return( (unsigned) err );
598 }
599
600 //===========================================================================================================================
601 // RMxSessionAccept
602 //===========================================================================================================================
603
604 DEBUG_LOCAL OSStatus RMxSessionInitServer( RMxSessionRef inSession )
605 {
606 OSStatus err;
607
608 inSession->sockEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
609 err = translate_errno( inSession->sockEvent, errno_compat(), kNoResourcesErr );
610 require_noerr( err, exit );
611
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 );
615
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 ) );
621
622 exit:
623 return( err );
624 }
625
626 //===========================================================================================================================
627 // RMxSessionInitClient
628 //===========================================================================================================================
629
630 DEBUG_LOCAL OSStatus RMxSessionInitClient( RMxSessionRef inSession, const char *inServer )
631 {
632 OSStatus err;
633 struct addrinfo hints;
634 struct addrinfo * addrList;
635 struct addrinfo * addr;
636 BOOL ok;
637
638 addrList = NULL;
639
640 // Initiate a connection to the server (if we're the client).
641
642 memset( &hints, 0, sizeof( hints ) );
643 hints.ai_family = AF_UNSPEC;
644 hints.ai_socktype = SOCK_STREAM;
645 hints.ai_protocol = IPPROTO_TCP;
646
647 err = getaddrinfo( inServer, kRMxServerPortString, &hints, &addrList );
648 require_noerr( err, exit );
649
650 for( addr = addrList; addr; addr = addr->ai_next )
651 {
652 check( addr->ai_addr && ( addr->ai_addrlen > 0 ) );
653
654 if( inSession->quit || ( gRMxState != kRMxStateRun ) )
655 {
656 dlog( kDebugLevelNotice, DEBUG_NAME "session state exit while connecting (quit=%d, state=%d)\n",
657 inSession->quit, gRMxState );
658 err = kEndingErr;
659 break;
660 }
661
662 // Clean up for any previous iteration that failed. Normal cleanup will occur when closing the session.
663
664 if( IsValidSocket( inSession->sock ) )
665 {
666 err = close_compat( inSession->sock );
667 check_translated_errno( err == 0, errno_compat(), kUnknownErr );
668 inSession->sock = kInvalidSocketRef;
669 }
670
671 if( inSession->sockEvent )
672 {
673 ok = CloseHandle( inSession->sockEvent );
674 check_translated_errno( ok, errno_compat(), kUnknownErr );
675 inSession->sockEvent = NULL;
676 }
677
678 // Set up the socket and try to connect.
679
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 );
683
684 inSession->sock = socket( addr->ai_family, addr->ai_socktype, addr->ai_protocol );
685 err = translate_errno( IsValidSocket( inSession->sock ), errno_compat(), kNoResourcesErr );
686 if( err != kNoErr )
687 {
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>",
690 err, err );
691 continue;
692 }
693
694 inSession->sockEvent = CreateEvent( NULL, FALSE, FALSE, NULL );
695 err = translate_errno( inSession->sockEvent, errno_compat(), kNoResourcesErr );
696 require_noerr( err, exit );
697
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 );
701
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 ) );
707
708 err = RMxSessionConnect( inSession, addr->ai_addr, addr->ai_addrlen );
709 if( err == kNoErr )
710 {
711 break;
712 }
713 }
714 require_action( addr, exit, err = kConnectionErr );
715
716 exit:
717 if( addrList )
718 {
719 freeaddrinfo( addrList );
720 }
721 return( err );
722 }
723
724 //===========================================================================================================================
725 // RMxSessionConnect
726 //===========================================================================================================================
727
728 DEBUG_LOCAL OSStatus RMxSessionConnect( RMxSessionRef inSession, const struct sockaddr *inAddr, size_t inAddrSize )
729 {
730 OSStatus err;
731 DWORD result;
732
733 check( inSession );
734 check( inSession->sock );
735 check( inSession->waitCount > 0 );
736 check( inAddr && ( inAddrSize > 0 ) );
737
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.
741
742 err = connect( inSession->sock, inAddr, (int) inAddrSize );
743 if( err == 0 ) goto exit;
744
745 // Wait for the connection to complete, timeout, or be canceled.
746
747 result = WaitForMultipleObjects( inSession->waitCount, inSession->waitHandles, FALSE, kRMxClientTimeout );
748 if( result == WAIT_OBJECT_0 ) // Socket Event
749 {
750 WSANETWORKEVENTS events;
751
752 // Check the result of the FD_CONNECT event to see if the connection was successful or not.
753
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 ];
758 }
759 else if( result == WAIT_TIMEOUT )
760 {
761 err = kTimeoutErr;
762 }
763 else
764 {
765 err = kEndingErr;
766 }
767
768 exit:
769 return( err );
770 }
771
772 //===========================================================================================================================
773 // RMxSessionSendMessage
774 //
775 // Warning: Assumes the RMx lock is held.
776 //===========================================================================================================================
777
778 OSStatus RMxSessionSendMessage( RMxSessionRef inSession, RMxOpCode inOpCode, OSStatus inStatus, const char *inFormat, ... )
779 {
780 OSStatus err;
781 va_list args1;
782 va_list args2;
783
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++).
786
787 va_start( args1, inFormat );
788 va_start( args2, inFormat );
789 err = RMxSessionSendMessageVAList( inSession, inOpCode, inStatus, inFormat, args1, args2 );
790 va_end( args1 );
791 va_end( args2 );
792 require_noerr( err, exit );
793
794 exit:
795 return( err );
796 }
797
798 //===========================================================================================================================
799 // RMxSessionSendMessageVAList
800 //
801 // Warning: Assumes the RMx lock is held.
802 //===========================================================================================================================
803
804 DEBUG_LOCAL OSStatus
805 RMxSessionSendMessageVAList(
806 RMxSessionRef inSession,
807 RMxOpCode inOpCode,
808 OSStatus inStatus,
809 const char * inFormat,
810 va_list inArgs1,
811 va_list inArgs2 )
812 {
813 OSStatus err;
814 RMxMessage msg;
815 uint8_t * bufferStorage;
816 uint8_t * buffer;
817 size_t bodySize;
818 size_t headerSize;
819 size_t size;
820 int n;
821
822 bufferStorage = NULL;
823 check( inSession );
824 check( IsValidSocket( inSession->sock ) );
825 check( inFormat );
826
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.
829
830 err = RMxPackedSizeVAList( &bodySize, inFormat, inArgs1 );
831 require_noerr( err, exit );
832
833 size = kRMxMessageHeaderSize + bodySize;
834 if( size <= sizeof( msg.storage ) )
835 {
836 buffer = msg.storage;
837 }
838 else
839 {
840 bufferStorage = (uint8_t *) malloc( size );
841 require_action( bufferStorage, exit, err = kNoMemoryErr );
842 buffer = bufferStorage;
843 }
844
845 // Build the message header.
846
847 err = RMxPack( buffer, kRMxMessageHeaderSize, &headerSize, "wwwwww",
848 kRMxSignatureVersion1, // signature
849 inOpCode, // opcode
850 kRMxFlagsNone, // flags
851 0, // xid
852 inStatus, // status
853 (uint32_t) bodySize ); // size
854 require_noerr( err, exit );
855 check( headerSize == kRMxMessageHeaderSize );
856
857 // Build the message body.
858
859 err = RMxPackVAList( buffer + kRMxMessageHeaderSize, size - kRMxMessageHeaderSize, &size, inFormat, inArgs2 );
860 require_noerr( err, exit );
861 check( size == bodySize );
862
863 // Send the entire message.
864
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 );
869
870 exit:
871 if( bufferStorage )
872 {
873 free( bufferStorage );
874 }
875 return( err );
876 }
877
878 //===========================================================================================================================
879 // RMxSessionRecvMessage
880 //
881 // Note: This routine maintains state within the session data structure and can resume partial message reception.
882 //===========================================================================================================================
883
884 OSStatus RMxSessionRecvMessage( RMxSessionRef inSession, DWORD inTimeout )
885 {
886 OSStatus err;
887 DWORD result;
888 int n;
889
890 for( ;; )
891 {
892 if( inSession->quit || ( gRMxState != kRMxStateRun ) )
893 {
894 dlog( kDebugLevelNotice, DEBUG_NAME "session recv state exit (quit=%d, state=%d)\n", inSession->quit, gRMxState );
895 err = kEndingErr;
896 goto exit;
897 }
898
899 // Wait for data to become available or another event to occur.
900
901 result = WaitForMultipleObjects( inSession->waitCount, inSession->waitHandles, FALSE, inTimeout );
902 if( result == WAIT_OBJECT_0 ) // Socket Event (i.e. data available to read)
903 {
904 n = recv( inSession->sock, (char *) inSession->messageRecvBufferPtr, inSession->messageRecvRemaining, 0 );
905 if( n > 0 )
906 {
907 inSession->messageRecvBufferPtr += n;
908 inSession->messageRecvRemaining -= n;
909 if( inSession->messageRecvRemaining == 0 )
910 {
911 // Complete chunk ready. If we haven't read the header yet, it's the header. Otherwise, it's the data.
912
913 if( !inSession->messageRecvHeaderDone )
914 {
915 // Parse the buffer into the message header structure and mark the header as complete.
916
917 err = RMxUnpack( inSession->messageRecvBuffer, kRMxMessageHeaderSize,
918 "wwwwww",
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;
929
930 // Set up to read the data section (if any). Use the inline message buffer if the data will fit.
931
932 if( inSession->message.recvSize > 0 )
933 {
934 if( inSession->message.recvSize <= sizeof( inSession->message.storage ) )
935 {
936 inSession->message.recvData = inSession->message.storage;
937 }
938 else
939 {
940 inSession->message.recvData = (uint8_t *) malloc( inSession->message.recvSize );
941 require_action( inSession->message.recvData, exit, err = kNoMemoryErr );
942 }
943 inSession->messageRecvBufferPtr = inSession->message.recvData;
944 inSession->messageRecvRemaining = (int) inSession->message.recvSize;
945 }
946 }
947 }
948 if( inSession->messageRecvHeaderDone && ( inSession->messageRecvRemaining == 0 ) )
949 {
950 // Complete message ready. Reset state for next message. Exit to allow message to be processed.
951
952 inSession->messageRecvBufferPtr = inSession->messageRecvBuffer;
953 inSession->messageRecvRemaining = kRMxMessageHeaderSize;
954 inSession->messageRecvHeaderDone = false;
955 break;
956 }
957 }
958 else
959 {
960 err = errno_compat();
961 dlog( kDebugLevelNotice, DEBUG_NAME "session recv peer disconnected (%d/%d %m)\n", n, err, err );
962 err = kConnectionErr;
963 goto exit;
964 }
965 }
966 else if( result == ( WAIT_OBJECT_0 + 1 ) ) // Close Event
967 {
968 dlog( kDebugLevelNotice, DEBUG_NAME "session recv close signaled\n" );
969 err = kEndingErr;
970 goto exit;
971 }
972 else if( result == ( WAIT_OBJECT_0 + 2 ) ) // State Change Event
973 {
974 dlog( kDebugLevelNotice, DEBUG_NAME "session recv state change (%d)\n", gRMxState );
975 err = kEndingErr;
976 goto exit;
977 }
978 else if( result == WAIT_TIMEOUT ) // Timeout
979 {
980 dlog( kDebugLevelNotice, DEBUG_NAME "session recv timeout\n" );
981 err = kTimeoutErr;
982 goto exit;
983 }
984 else
985 {
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;
989 goto exit;
990 }
991 }
992 err = kNoErr;
993
994 exit:
995 return( err );
996 }
997
998 #if 0
999 #pragma mark -
1000 #pragma mark == Utilities ==
1001 #endif
1002
1003 //===========================================================================================================================
1004 // RMxCheckVersion
1005 //===========================================================================================================================
1006
1007 OSStatus
1008 RMxCheckVersion(
1009 uint32_t inClientCurrentVersion, uint32_t inClientOldestClientVersion, uint32_t inClientOldestServerVersion,
1010 uint32_t inServerCurrentVersion, uint32_t inServerOldestClientVersion, uint32_t inServerOldestServerVersion )
1011 {
1012 OSStatus err;
1013 const char * message;
1014
1015 DEBUG_USE_ONLY( inClientOldestClientVersion );
1016 DEBUG_USE_ONLY( inServerOldestServerVersion );
1017 DEBUG_USE_ONLY( message );
1018
1019 // Determine if the version information on both sides is compatible.
1020
1021 if( inClientCurrentVersion == inServerCurrentVersion )
1022 {
1023 message = "versions exactly match";
1024 err = kNoErr;
1025 }
1026 else if( inClientCurrentVersion > inServerCurrentVersion )
1027 {
1028 if( inClientOldestServerVersion <= inServerCurrentVersion )
1029 {
1030 message = "client newer, but compatible";
1031 err = kNoErr;
1032 }
1033 else
1034 {
1035 message = "server too old for client";
1036 err = kIncompatibleErr;
1037 }
1038 }
1039 else
1040 {
1041 if( inServerOldestClientVersion <= inClientCurrentVersion )
1042 {
1043 message = "server newer, but compatible";
1044 err = kNoErr;
1045 }
1046 else
1047 {
1048 message = "client too old for server";
1049 err = kIncompatibleErr;
1050 }
1051 }
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 );
1055
1056 return( err );
1057 }
1058
1059 //===========================================================================================================================
1060 // RMxPackedSize
1061 //===========================================================================================================================
1062
1063 OSStatus RMxPackedSize( size_t *outSize, const char *inFormat, ... )
1064 {
1065 OSStatus err;
1066 va_list args;
1067
1068 va_start( args, inFormat );
1069 err = RMxPackedSizeVAList( outSize, inFormat, args );
1070 va_end( args );
1071
1072 return( err );
1073 }
1074
1075 //===========================================================================================================================
1076 // RMxPackedSizeVAList
1077 //===========================================================================================================================
1078
1079 OSStatus RMxPackedSizeVAList( size_t *outSize, const char *inFormat, va_list inArgs )
1080 {
1081 OSStatus err;
1082 size_t size;
1083 char c;
1084 const uint8_t * src;
1085 uint32_t tempU32;
1086 const uint8_t * p;
1087
1088 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1089
1090 size = 0;
1091
1092 // Loop thru each character in the format string, decode it, and add the size required to pack it.
1093
1094 for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
1095 {
1096 switch( c )
1097 {
1098 case 'b': // Byte (8-bit)
1099
1100 va_arg( inArgs, unsigned int );
1101 size += 1;
1102 break;
1103
1104 case 'h': // Half-word (16-bit)
1105
1106 va_arg( inArgs, unsigned int );
1107 size += 2;
1108 break;
1109
1110 case 'w': // Word (32-bit)
1111
1112 va_arg( inArgs, unsigned int );
1113 size += 4;
1114 break;
1115
1116 case 's': // UTF-8 String, null terminated
1117
1118 src = va_arg( inArgs, const uint8_t * );
1119 check( src );
1120
1121 p = src;
1122 while( *p++ != 0 ) {}
1123 size += ( p - src );
1124 break;
1125
1126 case 'n': // N bytes of raw data; 1st arg is size, 2nd arg is ptr; stored with 32-bit length prefix.
1127
1128 tempU32 = (uint32_t) va_arg( inArgs, unsigned int );
1129 src = va_arg( inArgs, const uint8_t * );
1130 check( src || ( tempU32 == 0 ) );
1131
1132 size += ( 4 + tempU32 );
1133 break;
1134
1135 case ' ': // Ignore spaces (they're just for format string readability).
1136 break;
1137
1138 default: // Unknown format specifier
1139
1140 err = kUnsupportedErr;
1141 goto exit;
1142 }
1143 }
1144
1145 // Success!
1146
1147 if( outSize )
1148 {
1149 *outSize = size;
1150 }
1151 err = kNoErr;
1152
1153 exit:
1154 return( err );
1155 }
1156
1157 //===========================================================================================================================
1158 // RMxPack
1159 //===========================================================================================================================
1160
1161 OSStatus RMxPack( void *inBuffer, size_t inMaxSize, size_t *outSize, const char *inFormat, ... )
1162 {
1163 OSStatus err;
1164 va_list args;
1165
1166 va_start( args, inFormat );
1167 err = RMxPackVAList( inBuffer, inMaxSize, outSize, inFormat, args );
1168 va_end( args );
1169
1170 return( err );
1171 }
1172
1173 //===========================================================================================================================
1174 // RMxPackVAList
1175 //===========================================================================================================================
1176
1177 OSStatus RMxPackVAList( void *inBuffer, size_t inMaxSize, size_t *outSize, const char *inFormat, va_list inArgs )
1178 {
1179 OSStatus err;
1180 char c;
1181 const uint8_t * src;
1182 uint8_t * dst;
1183 uint8_t * end;
1184 uint8_t tempU8;
1185 uint16_t tempU16;
1186 uint32_t tempU32;
1187 const uint8_t * p;
1188 size_t size;
1189
1190 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1191
1192 dst = (uint8_t *) inBuffer;
1193 end = dst + inMaxSize;
1194
1195 // Loop thru each character in the format string, decode it, and pack the data appropriately.
1196
1197 for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
1198 {
1199 switch( c )
1200 {
1201 case 'b': // Byte (8-bit)
1202
1203 check( ( end - dst ) >= 1 );
1204 tempU8 = (uint8_t) va_arg( inArgs, unsigned int );
1205 *dst++ = tempU8;
1206 break;
1207
1208 case 'h': // Half-word (16-bit)
1209
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 );
1214 break;
1215
1216 case 'w': // Word (32-bit)
1217
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 );
1224 break;
1225
1226 case 's': // UTF-8 String, null terminated
1227
1228 src = va_arg( inArgs, const uint8_t * );
1229 check( src );
1230
1231 p = src;
1232 while( *p++ != 0 ) {}
1233 size = (size_t)( p - src );
1234 check( ( end - dst ) >= (ptrdiff_t) size );
1235
1236 while( size-- > 0 )
1237 {
1238 *dst++ = *src++;
1239 }
1240 break;
1241
1242 case 'n': // N bytes of raw data; 1st arg is size, 2nd arg is ptr; stored with 32-bit length prefix.
1243
1244 tempU32 = (uint32_t) va_arg( inArgs, unsigned int );
1245 check( ( end - dst ) >= (ptrdiff_t)( 4 + tempU32 ) );
1246
1247 src = va_arg( inArgs, const uint8_t * );
1248 check( src || ( tempU32 == 0 ) );
1249
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 )
1255 {
1256 *dst++ = *src++;
1257 }
1258 break;
1259
1260 case ' ': // Ignore spaces (they're just for format string readability).
1261 break;
1262
1263 default: // Unknown format specifier
1264
1265 err = kUnsupportedErr;
1266 goto exit;
1267 }
1268 }
1269
1270 // Success!
1271
1272 if( outSize )
1273 {
1274 *outSize = (size_t)( dst - ( (uint8_t *) inBuffer ) );
1275 }
1276 err = kNoErr;
1277
1278 exit:
1279 return( err );
1280 }
1281
1282 //===========================================================================================================================
1283 // RMxUnpack
1284 //===========================================================================================================================
1285
1286 OSStatus RMxUnpack( const void *inData, size_t inSize, const char *inFormat, ... )
1287 {
1288 OSStatus err;
1289 va_list args;
1290
1291 va_start( args, inFormat );
1292 err = RMxUnpackVAList( inData, inSize, inFormat, args );
1293 va_end( args );
1294
1295 return( err );
1296 }
1297
1298 //===========================================================================================================================
1299 // DNSUnpackVAList
1300 //===========================================================================================================================
1301
1302 OSStatus RMxUnpackVAList( const void *inData, size_t inSize, const char *inFormat, va_list inArgs )
1303 {
1304 OSStatus err;
1305 char c;
1306 const uint8_t * src;
1307 const uint8_t * end;
1308 uint8_t * b;
1309 uint16_t * h;
1310 uint32_t * w;
1311 uint16_t tempU16;
1312 uint32_t tempU32;
1313 const uint8_t * p;
1314 size_t size;
1315 const uint8_t ** ptrArg;
1316 size_t * sizeArg;
1317
1318 check_compile_time_code( sizeof( unsigned int ) >= 4 );
1319
1320 src = (const uint8_t *) inData;
1321 end = src + inSize;
1322
1323 // Loop thru each character in the format string, decode it, and unpack the data appropriately.
1324
1325 for( c = *inFormat; c != '\0'; c = *( ++inFormat ) )
1326 {
1327 switch( c )
1328 {
1329 case 'b': // Byte (8-bit)
1330
1331 require_action( ( end - src ) >= 1, exit, err = kSizeErr );
1332 b = va_arg( inArgs, uint8_t * );
1333 if( b )
1334 {
1335 *b = *src;
1336 }
1337 ++src;
1338 break;
1339
1340 case 'h': // Half-word (16-bit)
1341
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 * );
1346 if( h )
1347 {
1348 *h = tempU16;
1349 }
1350 break;
1351
1352 case 'w': // Word (32-bit)
1353
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 * );
1360 if( w )
1361 {
1362 *w = tempU32;
1363 }
1364 break;
1365
1366 case 's': // UTF-8 String, null terminated; 1st arg=ptr to ptr, 2nd arg=ptr to size, excluding null terminator.
1367
1368 p = src;
1369 while( ( ( end - p ) > 0 ) && ( *p != 0 ) )
1370 {
1371 ++p;
1372 }
1373 require_action( ( end - p ) > 0, exit, err = kSizeErr );
1374 size = (size_t)( p - src );
1375
1376 ptrArg = va_arg( inArgs, const uint8_t ** );
1377 if( ptrArg )
1378 {
1379 *ptrArg = src;
1380 }
1381
1382 sizeArg = va_arg( inArgs, size_t * );
1383 if( sizeArg )
1384 {
1385 *sizeArg = size;
1386 }
1387
1388 src = p + 1;
1389 break;
1390
1391 case 'n': // N bytes of raw data; 1st arg is ptr to ptr, 2nd arg is ptr to size.
1392
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;
1400
1401 ptrArg = va_arg( inArgs, const uint8_t ** );
1402 if( ptrArg )
1403 {
1404 *ptrArg = src;
1405 }
1406
1407 sizeArg = va_arg( inArgs, size_t * );
1408 if( sizeArg )
1409 {
1410 *sizeArg = size;
1411 }
1412
1413 src += size;
1414 break;
1415
1416 case ' ': // Ignore spaces (they're just for format string readability).
1417 break;
1418
1419 default: // Unknown format specifier
1420
1421 err = kUnsupportedErr;
1422 goto exit;
1423 }
1424 }
1425
1426 // Success!
1427
1428 err = kNoErr;
1429
1430 exit:
1431 return( err );
1432 }
1433
1434 #if( DEBUG )
1435 //===========================================================================================================================
1436 // RMxPackUnpackTest
1437 //===========================================================================================================================
1438
1439 OSStatus RMxPackUnpackTest( void );
1440
1441 OSStatus RMxPackUnpackTest( void )
1442 {
1443 static const uint8_t data[] =
1444 {
1445 0xAA,
1446 0xBB, 0xCC,
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
1450 };
1451 OSStatus err;
1452 uint8_t buffer[ 128 ];
1453 size_t size;
1454 uint8_t b;
1455 uint16_t h;
1456 uint32_t w;
1457 char * s;
1458 size_t sSize;
1459 uint8_t * d;
1460 size_t dSize;
1461
1462 check_compile_time_code( sizeof( data ) >= 29 );
1463
1464 // simple API test.
1465 //
1466
1467 printf( "\nsimple API test\n" );
1468
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 );
1472
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 );
1477
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 );
1487
1488 printf( "\nsimple API test done\n\n" );
1489
1490 // Done
1491
1492 printf( "\n\nALL TESTS PASSED\n\n" );
1493
1494 exit:
1495 return( err );
1496 }
1497 #endif // DEBUG
1498
1499 #ifdef __cplusplus
1500 }
1501 #endif