porttcp.c
上传用户:kongshuqi
上传日期:2013-10-09
资源大小:59k
文件大小:19k
源码类别:

通讯编程

开发平台:

Visual C++

  1. /*
  2.  * FreeModbus Libary: Win32 Port
  3.  * Copyright (C) 2006 Christian Walter <wolti@sil.at>
  4.  *
  5.  * This library is free software; you can redistribute it and/or
  6.  * modify it under the terms of the GNU Lesser General Public
  7.  * License as published by the Free Software Foundation; either
  8.  * version 2.1 of the License, or (at your option) any later version.
  9.  *
  10.  * This library is distributed in the hope that it will be useful,
  11.  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  12.  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  13.  * Lesser General Public License for more details.
  14.  *
  15.  * You should have received a copy of the GNU Lesser General Public
  16.  * License along with this library; if not, write to the Free Software
  17.  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  18.  *
  19.  * File: $Id: porttcp.c,v 1.2 2006/06/26 19:24:07 wolti Exp $
  20.  */
  21. /*
  22.  * Design Notes:
  23.  *
  24.  * The xMBPortTCPInit function allocates a socket and binds the socket to
  25.  * all available interfaces ( bind with INADDR_ANY ). In addition it
  26.  * creates an array of event objects which is used to check the state of
  27.  * the clients. On event object is used to handle new connections or
  28.  * closed ones. The other objects are used on a per client basis for
  29.  * processing.
  30.  */
  31. #include <stdio.h>
  32. #include "winsock2.h"
  33. #include "port.h"
  34. /* ----------------------- Modbus includes ----------------------------------*/
  35. #include "mb.h"
  36. #include "mbport.h"
  37. /* ----------------------- MBAP Header --------------------------------------*/
  38. #define MB_TCP_UID          6
  39. #define MB_TCP_LEN          4
  40. #define MB_TCP_FUNC         7
  41. /* ----------------------- Defines  -----------------------------------------*/
  42. #define MB_TCP_DEFAULT_PORT 502 /* TCP listening port. */
  43. #define MB_TCP_POOL_TIMEOUT 50  /* pool timeout for event waiting. */
  44. #define MB_TCP_READ_TIMEOUT 1000        /* Maximum timeout to wait for packets. */
  45. #define MB_TCP_READ_CYCLE   100 /* Time between checking for new data. */
  46. #define MB_TCP_DEBUG        1   /* Set to 1 for additional debug output. */
  47. #define MB_TCP_BUF_SIZE     ( 256 + 7 ) /* Must hold a complete Modbus TCP frame. */
  48. #define EV_CONNECTION       0
  49. #define EV_CLIENT           1
  50. #define EV_NEVENTS          EV_CLIENT + 1
  51. /* ----------------------- Static variables ---------------------------------*/
  52. SOCKET          xListenSocket;
  53. SOCKET          xClientSocket;
  54. WSAEVENT        xEvents[EV_NEVENTS];
  55. static UCHAR    aucTCPBuf[MB_TCP_BUF_SIZE];
  56. static USHORT   usTCPBufPos;
  57. static USHORT   usTCPFrameBytesLeft;
  58. /* ----------------------- External functions -------------------------------*/
  59. TCHAR          *WsaError2String( DWORD dwError );
  60. /* ----------------------- Static functions ---------------------------------*/
  61. BOOL            prvMBTCPPortAddressToString( SOCKET xSocket, LPTSTR szAddr, USHORT usBufSize );
  62. LPTSTR          prvMBTCPPortFrameToString( UCHAR * pucFrame, USHORT usFrameLen );
  63. static BOOL     prvbMBPortAcceptClient( void );
  64. static void     prvvMBPortReleaseClient( void );
  65. static BOOL     prvMBTCPGetFrame( void );
  66. /* ----------------------- Begin implementation -----------------------------*/
  67. BOOL
  68. xMBTCPPortInit( USHORT usTCPPort )
  69. {
  70.     BOOL            bOkay = FALSE;
  71.     USHORT          usPort;
  72.     SOCKADDR_IN     xService;
  73.     WSADATA         wsaData;
  74.     int             i;
  75.     if( WSAStartup( MAKEWORD( 2, 2 ), &wsaData ) != 0 )
  76.     {
  77.         return FALSE;
  78.     }
  79.     if( usTCPPort == 0 )
  80.     {
  81.         usPort = MB_TCP_DEFAULT_PORT;
  82.     }
  83.     else
  84.     {
  85.         usPort = ( USHORT ) usTCPPort;
  86.     }
  87.     xService.sin_family = AF_INET;
  88.     xService.sin_port = htons( usPort );
  89.     xService.sin_addr.s_addr = INADDR_ANY;
  90.     xClientSocket = INVALID_SOCKET;
  91.     for( i = 0; i < EV_NEVENTS; i++ )
  92.     {
  93.         if( ( xEvents[i] = WSACreateEvent(  ) ) == WSA_INVALID_EVENT )
  94.             break;
  95.     }
  96.     if( i != EV_NEVENTS )
  97.     {
  98.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't create event objects: %srn" ),
  99.                     WsaError2String( WSAGetLastError(  ) ) );
  100.     }
  101.     else if( ( xListenSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP ) ) == INVALID_SOCKET )
  102.     {
  103.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't create socket: %srn" ),
  104.                     WsaError2String( WSAGetLastError(  ) ) );
  105.     }
  106.     else if( bind( xListenSocket, ( SOCKADDR * ) & xService, sizeof( xService ) ) == SOCKET_ERROR )
  107.     {
  108.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't bind on socket: %srn" ),
  109.                     WsaError2String( WSAGetLastError(  ) ) );
  110.     }
  111.     else if( listen( xListenSocket, 5 ) == SOCKET_ERROR )
  112.     {
  113.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't listen on socket: %srn" ),
  114.                     WsaError2String( WSAGetLastError(  ) ) );
  115.     }
  116.     else if( WSAEventSelect( xListenSocket, xEvents[EV_CONNECTION], FD_ACCEPT ) == SOCKET_ERROR )
  117.     {
  118.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't enable events on socket: %srn" ),
  119.                     WsaError2String( WSAGetLastError(  ) ) );
  120.     }
  121.     else
  122.     {
  123.         vMBPortLog( MB_LOG_INFO, _T( "TCP-POLL" ), _T( "Modbus TCP server listening on %S:%drn" ),
  124.                     inet_ntoa( xService.sin_addr ), ntohs( xService.sin_port ) );
  125.         bOkay = TRUE;
  126.     }
  127.     /* Perform cleanup on error. */
  128.     if( bOkay != TRUE )
  129.     {
  130.         for( i = 0; i < EV_NEVENTS; i++ )
  131.         {
  132.             if( xEvents[i] != WSA_INVALID_EVENT )
  133.                 WSACloseEvent( xEvents[i] );
  134.         }
  135.         if( xListenSocket != SOCKET_ERROR )
  136.             closesocket( xListenSocket );
  137.     }
  138.     return bOkay;
  139. }
  140. void
  141. vMBTCPPortClose(  )
  142. {
  143.     int             i;
  144.     /* Release all event handlers. */
  145.     for( i = 0; i < EV_NEVENTS; i++ )
  146.     {
  147.         if( xEvents[i] != WSA_INVALID_EVENT )
  148.             WSACloseEvent( xEvents[ i ] );
  149.     }
  150.     /* Close all client sockets. */
  151.     if( xClientSocket != SOCKET_ERROR )
  152.     {
  153.         prvvMBPortReleaseClient(  );
  154.     }
  155.     /* Close the listener socket. */
  156.     if( xListenSocket != SOCKET_ERROR )
  157.     {
  158.         closesocket( xListenSocket );
  159.     }
  160.     ( void )WSACleanup(  );
  161. }
  162. void
  163. vMBTCPPortDisable( void )
  164. {
  165.     /* Close all client sockets. */
  166.     if( xClientSocket != SOCKET_ERROR )
  167.     {
  168.         prvvMBPortReleaseClient(  );
  169.     }
  170. }
  171. /*! ingroup port_win32tcp
  172.  *
  173.  * brief Pool the listening socket and currently connected Modbus TCP clients
  174.  *   for new events.
  175.  * internal
  176.  *
  177.  * This function checks if new clients want to connect or if already connected 
  178.  * clients are sending requests. If a new client is connected and there are 
  179.  * still client slots left (The current implementation supports only one)
  180.  * then the connection is accepted and an event object for the new client
  181.  * socket is activated (See prvbMBPortAcceptClient() ).
  182.  * Events for already existing clients in c FD_READ and c FD_CLOSE. In case of
  183.  * an c FD_CLOSE the client connection is released (See prvvMBPortReleaseClient() ).
  184.  * In case of an c FD_READ command the existing data is read from the client
  185.  * and if a complete frame has been received the Modbus Stack is notified.
  186.  *
  187.  * return FALSE in case of an internal I/O error. For example if the internal
  188.  *   event objects are in an invalid state. Note that this does not include any 
  189.  *   client errors. In all other cases returns TRUE.
  190.  */
  191. BOOL
  192. xMBPortTCPPool( void )
  193. {
  194.     BOOL            bOkay = TRUE;
  195.     DWORD           dwWaitResult;
  196.     WSANETWORKEVENTS xNetworkEvents;
  197.     int             iEventNr;
  198.     int             iRes;
  199.     dwWaitResult = WSAWaitForMultipleEvents( EV_NEVENTS, xEvents, FALSE,
  200.                                              MB_TCP_POOL_TIMEOUT, FALSE );
  201.     /* Do nothing because only the timeout has expired. */
  202.     if( ( dwWaitResult == WAIT_IO_COMPLETION ) || ( dwWaitResult == WSA_WAIT_TIMEOUT ) )
  203.     {
  204.     }
  205.     /* Waiting for events failed. */
  206.     else if( dwWaitResult == WSA_WAIT_FAILED )
  207.     {
  208.         vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't wait for network events: %s" ),
  209.                     WsaError2String( WSAGetLastError(  ) ) );
  210.         bOkay = FALSE;
  211.     }
  212.     /* A event occured on one of the sockets */
  213.     else
  214.     {
  215.         /* Get the event number for the result of the wait operation. */
  216.         iEventNr = dwWaitResult - WSA_WAIT_EVENT_0;
  217.         /* A client wants to connect. */
  218.         if( iEventNr == EV_CONNECTION )
  219.         {
  220.             if( MB_TCP_DEBUG )
  221.             {
  222.                 vMBPortLog( MB_LOG_DEBUG, _T( "TCP-POLL" ), _T( "got EV_CONNECTION eventrn" ) );
  223.             }
  224.             /* Get additional event information from the socket. In addition the event
  225.              * object is reseted. 
  226.              */
  227.             iRes = WSAEnumNetworkEvents( xListenSocket, xEvents[iEventNr], &xNetworkEvents );
  228.             if( iRes == SOCKET_ERROR )
  229.             {
  230.                 vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't get event list: %Srn" ),
  231.                             WsaError2String( WSAGetLastError(  ) ) );
  232.             }
  233.             else if( xNetworkEvents.lNetworkEvents & FD_ACCEPT )
  234.             {
  235.                 /* A new connection from a client. Accept it. */
  236.                 ( void )prvbMBPortAcceptClient(  );
  237.             }
  238.         }
  239.         /* An already connected client has new data or the connection has 
  240.          * been closed. */
  241.         else if( iEventNr == EV_CLIENT )
  242.         {
  243.             if( MB_TCP_DEBUG )
  244.             {
  245.                 vMBPortLog( MB_LOG_DEBUG, _T( "TCP-POLL" ), _T( "got EV_CLIENT eventrn" ) );
  246.             }
  247.             iRes = WSAEnumNetworkEvents( xClientSocket, xEvents[iEventNr], &xNetworkEvents );
  248.             if( iRes == SOCKET_ERROR )
  249.             {
  250.                 vMBPortLog( MB_LOG_ERROR, _T( "TCP-POLL" ), _T( "can't get event list: %srn" ),
  251.                             WsaError2String( WSAGetLastError(  ) ) );
  252.             }
  253.             else if( xNetworkEvents.lNetworkEvents & FD_READ )
  254.             {
  255.                 if( MB_TCP_DEBUG )
  256.                 {
  257.                     vMBPortLog( MB_LOG_DEBUG, _T( "TCP-POLL" ), _T( "FD_READ eventrn" ) );
  258.                 }
  259.                 /* Process part of the Modbus TCP frame. In case of an I/O error we have to drop
  260.                  * the client connection.
  261.                  */
  262.                 if( prvMBTCPGetFrame(  ) != TRUE )
  263.                 {
  264.                     prvvMBPortReleaseClient(  );
  265.                 }
  266.             }
  267.             else if( xNetworkEvents.lNetworkEvents & FD_CLOSE )
  268.             {
  269.                 if( MB_TCP_DEBUG )
  270.                 {
  271.                     vMBPortLog( MB_LOG_DEBUG, _T( "TCP-POLL" ), _T( "FD_CLOSE eventrn" ) );
  272.                 }
  273.                 prvvMBPortReleaseClient(  );
  274.             }
  275.             else
  276.             {
  277.                 vMBPortLog( MB_LOG_WARN, _T( "TCP-POLL" ), _T( "unknown EV_CLIENT eventrn" ) );
  278.             }
  279.         }
  280.         else
  281.         {
  282.             /* Error - Log a warning. */
  283.         }
  284.     }
  285.     return bOkay;
  286. }
  287. /*!
  288.  * ingroup port_win32tcp
  289.  * brief Receives parts of a Modbus TCP frame and if complete notifies
  290.  *    the protocol stack.
  291.  * internal 
  292.  *
  293.  * This function reads a complete Modbus TCP frame from the protocol stack.
  294.  * It starts by reading the header with an initial request size for
  295.  * usTCPFrameBytesLeft = MB_TCP_FUNC. If the header is complete the 
  296.  * number of bytes left can be calculated from it (See Length in MBAP header).
  297.  * Further read calls are issued until the frame is complete.
  298.  *
  299.  * return c TRUE if part of a Modbus TCP frame could be processed. In case
  300.  *   of a communication error the function returns c FALSE.
  301.  */
  302. BOOL
  303. prvMBTCPGetFrame(  )
  304. {
  305.     BOOL            bOkay = TRUE;
  306.     USHORT          usLength;
  307.     int             iRes;
  308.     LPTSTR          szFrameAsStr;
  309.     /* Make sure that we can safely process the next read request. If there
  310.      * is an overflow drop the client.
  311.      */
  312.     if( ( usTCPBufPos + usTCPFrameBytesLeft ) >= MB_TCP_BUF_SIZE )
  313.     {
  314.         vMBPortLog( MB_LOG_WARN, _T( "MBTCP-RCV" ),
  315.                     _T( "Detected buffer overrun. Dropping client.rn" ) );
  316.         return FALSE;
  317.     }
  318.     iRes = recv( xClientSocket, &aucTCPBuf[usTCPBufPos], usTCPFrameBytesLeft, 0 );
  319.     switch ( iRes )
  320.     {
  321.     case SOCKET_ERROR:
  322.         vMBPortLog( MB_LOG_WARN, _T( "MBTCP-RCV" ), _T( "recv failed: %srn" ),
  323.                     WsaError2String( WSAGetLastError(  ) ) );
  324.         if( WSAGetLastError(  ) != WSAEWOULDBLOCK )
  325.         {
  326.             bOkay = FALSE;
  327.         }
  328.         break;
  329.     case 0:
  330.         bOkay = FALSE;
  331.         break;
  332.     default:
  333.         usTCPBufPos += iRes;
  334.         usTCPFrameBytesLeft -= iRes;
  335.     }
  336.     /* If we have received the MBAP header we can analyze it and calculate
  337.      * the number of bytes left to complete the current request. If complete
  338.      * notify the protocol stack.
  339.      */
  340.     if( usTCPBufPos >= MB_TCP_FUNC )
  341.     {
  342.         /* Length is a byte count of Modbus PDU (function code + data) and the
  343.          * unit identifier. */
  344.         usLength = aucTCPBuf[MB_TCP_LEN] << 8U;
  345.         usLength |= aucTCPBuf[MB_TCP_LEN + 1];
  346.         /* Is the frame already complete. */
  347.         if( usTCPBufPos < ( MB_TCP_UID + usLength ) )
  348.         {
  349.             usTCPFrameBytesLeft = usLength + MB_TCP_UID - usTCPBufPos;
  350.         }
  351.         /* The frame is complete. */
  352.         else if( usTCPBufPos == ( MB_TCP_UID + usLength ) )
  353.         {
  354.             if( MB_TCP_DEBUG )
  355.             {
  356.                 szFrameAsStr = prvMBTCPPortFrameToString( aucTCPBuf, usTCPBufPos );
  357.                 if( szFrameAsStr != NULL )
  358.                 {
  359.                     vMBPortLog( MB_LOG_DEBUG, _T( "MBTCP-RCV" ), _T( "Received: %srn" ),
  360.                                 szFrameAsStr );
  361.                     free( szFrameAsStr );
  362.                 }
  363.             }
  364.             ( void )xMBPortEventPost( EV_FRAME_RECEIVED );
  365.         }
  366.         /* This can not happend because we always calculate the number of bytes
  367.          * to receive. */
  368.         else
  369.         {
  370.             assert( usTCPBufPos <= ( MB_TCP_UID + usLength ) );
  371.         }
  372.     }
  373.     return bOkay;
  374. }
  375. BOOL
  376. xMBTCPPortGetRequest( UCHAR ** ppucMBTCPFrame, USHORT * usTCPLength )
  377. {
  378.     *ppucMBTCPFrame = &aucTCPBuf[0];
  379.     *usTCPLength = usTCPBufPos;
  380.     /* Reset the buffer. */
  381.     usTCPBufPos = 0;
  382.     usTCPFrameBytesLeft = MB_TCP_FUNC;
  383.     return TRUE;
  384. }
  385. BOOL
  386. xMBTCPPortSendResponse( const UCHAR * pucMBTCPFrame, USHORT usTCPLength )
  387. {
  388.     BOOL            bFrameSent = FALSE;
  389.     BOOL            bAbort = FALSE;
  390.     int             res;
  391.     int             iBytesSent = 0;
  392.     int             iTimeOut = MB_TCP_READ_TIMEOUT;
  393.     LPTSTR          szFrameAsStr;
  394.     if( MB_TCP_DEBUG )
  395.     {
  396.         szFrameAsStr = prvMBTCPPortFrameToString( aucTCPBuf, usTCPLength );
  397.         if( szFrameAsStr != NULL )
  398.         {
  399.             vMBPortLog( MB_LOG_DEBUG, _T( "MBTCP-SND" ), _T( "Snd: %srn" ), szFrameAsStr );
  400.             free( szFrameAsStr );
  401.         }
  402.     }
  403.     do
  404.     {
  405.         res = send( xClientSocket, &pucMBTCPFrame[iBytesSent], usTCPLength - iBytesSent, 0 );
  406.         switch ( res )
  407.         {
  408.         case SOCKET_ERROR:
  409.             if( ( WSAGetLastError(  ) == WSAEWOULDBLOCK ) && ( iTimeOut > 0 ) )
  410.             {
  411.                 iTimeOut -= MB_TCP_READ_CYCLE;
  412.                 Sleep( MB_TCP_READ_CYCLE );
  413.             }
  414.             else
  415.             {
  416.                 bAbort = TRUE;
  417.             }
  418.             break;
  419.         case 0:
  420.             prvvMBPortReleaseClient(  );
  421.             bAbort = TRUE;
  422.             break;
  423.         default:
  424.             iBytesSent += res;
  425.             break;
  426.         }
  427.     }
  428.     while( ( iBytesSent != usTCPLength ) && !bAbort );
  429.     bFrameSent = iBytesSent == usTCPLength ? TRUE : FALSE;
  430.     return bFrameSent;
  431. }
  432. void
  433. prvvMBPortReleaseClient(  )
  434. {
  435.     TCHAR           szIPAddr[32];
  436.     if( prvMBTCPPortAddressToString( xClientSocket, szIPAddr, _countof( szIPAddr ) ) == TRUE )
  437.     {
  438.         vMBPortLog( MB_LOG_INFO, _T( "MBTCP-CMGT" ), _T( "client %s disconnected.rn" ),
  439.                     szIPAddr );
  440.     }
  441.     else
  442.     {
  443.         vMBPortLog( MB_LOG_INFO, _T( "MBTCP-CMGT" ), _T( "unknown client disconnected.rn" ) );
  444.     }
  445.     /* Disable event notification for this client socket. */
  446.     if( WSAEventSelect( xClientSocket, xEvents[EV_CLIENT], 0 ) == SOCKET_ERROR )
  447.     {
  448.         vMBPortLog( MB_LOG_ERROR, _T( "MBTCP-CMGT" ),
  449.                     _T( "can't disable events for disconnecting client socket: %srn" ),
  450.                     WsaError2String( WSAGetLastError(  ) ) );
  451.     }
  452.     /* Reset event object in case an event was still pending. */
  453.     if( WSAResetEvent( xEvents[EV_CLIENT] ) == SOCKET_ERROR )
  454.     {
  455.         vMBPortLog( MB_LOG_ERROR, _T( "MBTCP-CMGT" ),
  456.                     _T( "can't disable events for disconnecting client socket: %srn" ),
  457.                     WsaError2String( WSAGetLastError(  ) ) );
  458.     }
  459.     /* Disallow the sender side. This tells the other side that we have finished. */
  460.     if( shutdown( xClientSocket, SD_SEND ) == SOCKET_ERROR )
  461.     {
  462.         vMBPortLog( MB_LOG_ERROR, _T( "MBTCP-CMGT" ), _T( "shutdown failed: %srn" ),
  463.                     WsaError2String( WSAGetLastError(  ) ) );
  464.     }
  465.     /* Read any unread data from the socket. Note that this is not the strictly 
  466.      * correct way to do it because our sockets are non blocking and therefore
  467.      * some bytes could remain.
  468.      */
  469.     ( void )recv( xClientSocket, &aucTCPBuf[0], MB_TCP_BUF_SIZE, 0 );
  470.     ( void )closesocket( xClientSocket );
  471.     xClientSocket = INVALID_SOCKET;
  472. }
  473. BOOL
  474. prvbMBPortAcceptClient(  )
  475. {
  476.     SOCKET          xNewSocket;
  477.     BOOL            bOkay;
  478.     TCHAR           szIPAddr[32];
  479.     /* Check if we can handle a new connection. */
  480.     if( xClientSocket != INVALID_SOCKET )
  481.     {
  482.         vMBPortLog( MB_LOG_ERROR, _T( "MBTCP-CMGT" ),
  483.                     _T( "can't accept new client. all connections in use.rn" ) );
  484.         bOkay = FALSE;
  485.     }
  486.     else if( ( xNewSocket = accept( xListenSocket, NULL, NULL ) ) == INVALID_SOCKET )
  487.     {
  488.         bOkay = FALSE;
  489.     }
  490.     /* Register READ events on the socket file descriptor. */
  491.     else if( WSAEventSelect( xNewSocket, xEvents[EV_CLIENT], FD_READ | FD_CLOSE ) == SOCKET_ERROR )
  492.     {
  493.         bOkay = FALSE;
  494.         ( void )closesocket( xNewSocket );
  495.     }
  496.     /* Everything okay - Register the client connection. */
  497.     else
  498.     {
  499.         xClientSocket = xNewSocket;
  500.         usTCPBufPos = 0;
  501.         usTCPFrameBytesLeft = MB_TCP_FUNC;
  502.         if( prvMBTCPPortAddressToString( xClientSocket, szIPAddr, _countof( szIPAddr ) ) == TRUE )
  503.         {
  504.             vMBPortLog( MB_LOG_INFO, _T( "MBTCP-CMGT" ), _T( "accepted new client %s.rn" ),
  505.                         szIPAddr );
  506.         }
  507.         else
  508.         {
  509.             vMBPortLog( MB_LOG_INFO, _T( "MBTCP-CMGT" ), _T( "accepted unknown client.rn" ) );
  510.         }
  511.         bOkay = TRUE;
  512.     }
  513.     return bOkay;
  514. }