1 /******************************************************************
3 * Copyright 2014 Samsung Electronics All Rights Reserved.
7 * Licensed under the Apache License, Version 2.0 (the "License");
8 * you may not use this file except in compliance with the License.
9 * You may obtain a copy of the License at
11 * http://www.apache.org/licenses/LICENSE-2.0
13 * Unless required by applicable law or agreed to in writing, software
14 * distributed under the License is distributed on an "AS IS" BASIS,
15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16 * See the License for the specific language governing permissions and
17 * limitations under the License.
19 ******************************************************************/
20 #include "cawifiinterface.h"
22 #include <sys/types.h>
23 #include <sys/socket.h>
26 #include <sys/select.h>
27 #include <arpa/inet.h>
28 #include <netinet/in.h>
32 #include "caadapterutils.h"
34 #include "caadapternetdtls.h"
39 * @def WIFI_SERVER_TAG
40 * @brief Logging tag for module name
42 #define WIFI_SERVER_TAG "WIFI_SERVER"
45 * @def CA_UDP_BIND_RETRY_COUNT
46 * @brief Retry count in case of socket bind failure.
48 #define CA_UDP_BIND_RETRY_COUNT 10
52 * @brief max length for ip
57 * @var gUnicastServerSocketFD
58 * @brief Unicast server socket descriptor
60 static int32_t gUnicastServerSocketFD = -1;
63 * @var gMutexUnicastServer
64 * @brief Mutex to synchronize unicast server
66 static u_mutex gMutexUnicastServer = NULL;
70 * @brief Flag to control the Receive Unicast Data Thread
72 static bool gStopUnicast = false;
75 * @var gMulticastServerSocketFD
76 * @brief socket descriptor for multicast server
78 static int32_t gMulticastServerSocketFD = -1;
81 * @var gMutexMulticastServer
82 * @brief Mutex to synchronize secure multicast server
84 static u_mutex gMutexMulticastServer = NULL;
88 * @brief Flag to control the Receive Multicast Data Thread
90 static bool gStopMulticast = false;
94 * @var gSecureUnicastServerSocketFD
95 * @brief Secure unicast server socket descriptor
97 static int32_t gSecureUnicastServerSocketFD = -1;
100 * @var gMutexSecureUnicastServer
101 * @brief Mutex to synchronize secure unicast server
103 static u_mutex gMutexSecureUnicastServer = NULL;
106 * @var gStopSecureUnicast
107 * @brief Flag to control the unicast secure data receive thread
109 static bool gStopSecureUnicast = false;
114 * @brief ThreadPool for storing u_thread_pool_t handle passed from adapter
116 static u_thread_pool_t gThreadPool = NULL;
119 * @var gMulticastServerInterface
120 * @brief Local interface on which multicast server is running
122 static char gMulticastServerInterface[IPNAMESIZE];
125 * @var gMulticastMemberReq
126 * @brief ip_mreq structure passed to join a multicast group
128 static struct ip_mreq gMulticastMemberReq;
131 * @var gPacketReceivedCallback
132 * @brief Callback for notifying the upper layer on receival data from remote OIC device
134 static CAWiFiPacketReceivedCallback gPacketReceivedCallback = NULL;
137 * @var gExceptionCallback
138 * @brief Callback for notifying the upper layer when unicast/multicast server encounters exception
140 static CAWiFiExceptionCallback gExceptionCallback = NULL;
143 @brief Thread context information for unicast, multicast and secured unicast server
149 CAAdapterServerType_t type;
150 } CAAdapterReceiveThreadContext_t;
152 static void CAReceiveHandler(void *data)
154 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
156 VERIFY_NON_NULL_VOID(data, WIFI_SERVER_TAG, "Invalid thread context");
158 CAAdapterReceiveThreadContext_t *ctx = (CAAdapterReceiveThreadContext_t *)data;
160 struct timeval timeout;
161 char recvBuffer[COAP_MAX_PDU_SIZE] = {0};
163 while (true != *(ctx->stopFlag))
169 FD_SET(ctx->socket_fd, &reads);
171 int32_t ret = select(ctx->socket_fd + 1, &reads, NULL, NULL, &timeout);
172 if (*(ctx->stopFlag) == true)
174 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "Stop request received for [%d] server", ctx->type);
179 OIC_LOG_V(FATAL, WIFI_SERVER_TAG, "select returned error %s", strerror(errno));
182 if (!FD_ISSET(ctx->socket_fd, &reads))
187 memset(recvBuffer, 0, sizeof(recvBuffer));
189 // Read data from socket
190 struct sockaddr_in srcSockAddress;
192 socklen_t srcAddressLen = sizeof(srcSockAddress);
193 if (-1 == (recvLen = recvfrom(ctx->socket_fd, recvBuffer,
194 sizeof(recvBuffer), 0, (struct sockaddr *) &srcSockAddress,
197 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "%s", strerror(errno));
200 else if (0 == recvLen)
202 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Server socket shutdown [%d]!", ctx->type);
204 // Notify upper layer this exception
205 if (gExceptionCallback)
207 gExceptionCallback(ctx->type);
213 const char *srcIPAddress = NULL;
214 int32_t srcPort = -1;
216 srcIPAddress = inet_ntoa(srcSockAddress.sin_addr);
217 srcPort = ntohs(srcSockAddress.sin_port);
219 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "Received packet from %s:%d\n",
220 srcIPAddress, srcPort);
221 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "Data: %s\t, DataLength: %d\n",
222 recvBuffer, recvLen);
224 char *netMask = NULL;
225 if (CA_STATUS_OK != CAWiFiGetInterfaceSubnetMask(&netMask))
227 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to get ethernet subnet");
231 if (!CAAdapterIsSameSubnet(gMulticastServerInterface, srcIPAddress, netMask))
233 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "Packet received from different subnet, Ignore!");
245 case CA_UNICAST_SERVER:
246 case CA_MULTICAST_SERVER:
247 // Notify data to upper layer
248 if (gPacketReceivedCallback)
250 gPacketReceivedCallback(srcIPAddress, srcPort, recvBuffer, recvLen, false);
254 case CA_SECURED_UNICAST_SERVER:
256 CAResult_t ret = CAAdapterNetDtlsDecrypt(srcIPAddress,
258 (uint8_t *)recvBuffer,
260 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "CAAdapterNetDtlsDecrypt returns [%d]", ret);
263 #endif //__WITH_DTLS__
265 // Should never occur
266 OIC_LOG_V(DEBUG, WIFI_SERVER_TAG, "Invalid server type");
275 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
278 static CAResult_t CAWiFiCreateSocket(int32_t *socketFD, const char *localIp, int16_t *port,
279 const bool forceStart)
282 // Create a UDP socket
283 if (-1 == (sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)))
285 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to create Socket, Error code: %s",
287 return CA_STATUS_FAILED;
290 // Make the socket non-blocking
291 if (-1 == fcntl(sock, F_SETFL, O_NONBLOCK))
293 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to set non-block mode, Error code: %s",
297 return CA_STATUS_FAILED;
300 if (true == forceStart)
302 int32_t setOptionOn = 1;
303 if (-1 == setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
304 (char *) &setOptionOn,
305 sizeof(setOptionOn)))
307 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to set SO_REUSEADDR! Error code: %s",
311 return CA_STATUS_FAILED;
315 struct sockaddr_in sockAddr;
316 bool isBound = false;
317 int16_t serverPort = *port;
319 memset((char *) &sockAddr, 0, sizeof(sockAddr));
320 sockAddr.sin_family = AF_INET;
321 sockAddr.sin_port = htons(serverPort);
324 sockAddr.sin_addr.s_addr = inet_addr(localIp);
328 sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
332 for (i = 0; i < CA_UDP_BIND_RETRY_COUNT; i++)
334 if (-1 == bind(sock, (struct sockaddr *) &sockAddr,
337 if (false == forceStart)
339 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to bind socket[%s]. Trying again..",
342 //Set the port to next one
344 sockAddr.sin_port = htons(serverPort);
349 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to bind socket[%s]!",
359 if (false == isBound)
362 return CA_STATUS_FAILED;
370 static CAResult_t CAWiFiCloseSocket(int32_t *socketFD)
374 OIC_LOG(INFO, WIFI_SERVER_TAG, "Server not running");
375 return CA_SERVER_NOT_STARTED;
379 if (-1 == close(*socketFD))
381 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to close the socket, Error code: %s\n",
383 return CA_STATUS_FAILED;
390 static CAResult_t CAStartUnicastServer(const char *localAddress, int16_t *port,
391 const bool forceStart, bool isSecured, int32_t *serverFD)
393 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
395 CAResult_t ret = CAWiFiCreateSocket(serverFD, localAddress, port, forceStart);
396 if (CA_STATUS_OK != ret)
398 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to create unicast socket");
403 * The task to listen for data from unicast socket is added to the thread pool.
404 * This is a blocking call is made where we try to receive some data..
405 * We will keep waiting until some data is received.
406 * This task will be terminated when thread pool is freed on stopping the adapters.
407 * Thread context will be freed by thread on exit.
409 CAAdapterReceiveThreadContext_t *ctx = (CAAdapterReceiveThreadContext_t *)
410 OICMalloc(sizeof(CAAdapterReceiveThreadContext_t));
413 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Out of memory!");
415 return CA_MEMORY_ALLOC_FAILED;
418 ctx->stopFlag = &gStopUnicast;
419 ctx->socket_fd = *serverFD;
420 ctx->type = isSecured ? CA_SECURED_UNICAST_SERVER : CA_UNICAST_SERVER;
421 if (CA_STATUS_OK != u_thread_pool_add_task(gThreadPool, CAReceiveHandler, (void *)ctx))
423 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to create read thread!");
424 OICFree((void *)ctx);
426 return CA_STATUS_FAILED;
429 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
433 static void CAWiFiServerDestroyMutex(void)
435 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
437 if (gMutexUnicastServer)
439 u_mutex_free(gMutexUnicastServer);
440 gMutexUnicastServer = NULL;
444 if (gMutexSecureUnicastServer)
446 u_mutex_free(gMutexSecureUnicastServer);
447 gMutexSecureUnicastServer = NULL;
451 if (gMutexMulticastServer)
453 u_mutex_free(gMutexMulticastServer);
454 gMutexMulticastServer = NULL;
457 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
460 static CAResult_t CAWiFiServerCreateMutex(void)
462 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
464 gMutexUnicastServer = u_mutex_new();
465 if (!gMutexUnicastServer)
467 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to created mutex!");
468 return CA_STATUS_FAILED;
472 gMutexSecureUnicastServer = u_mutex_new();
473 if (!gMutexSecureUnicastServer)
475 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to created mutex!");
477 CAWiFiServerDestroyMutex();
478 return CA_STATUS_FAILED;
482 gMutexMulticastServer = u_mutex_new();
483 if (!gMutexMulticastServer)
485 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to created mutex!");
487 CAWiFiServerDestroyMutex();
488 return CA_STATUS_FAILED;
491 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
495 CAResult_t CAWiFiInitializeServer(const u_thread_pool_t threadPool)
497 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
500 VERIFY_NON_NULL(threadPool, WIFI_SERVER_TAG, "Thread pool handle is NULL");
503 if (CA_STATUS_OK != CAWiFiServerCreateMutex())
505 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to create mutex!");
506 return CA_STATUS_FAILED;
509 gThreadPool = threadPool;
511 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
515 void CAWiFiTerminateServer(void)
517 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
522 CAWiFiServerDestroyMutex();
524 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
527 CAResult_t CAWiFiStartUnicastServer(const char *localAddress, int16_t *port,
528 const bool forceStart, const CABool_t isSecured,
531 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
534 VERIFY_NON_NULL(localAddress, WIFI_SERVER_TAG, "localAddress");
535 VERIFY_NON_NULL(port, WIFI_SERVER_TAG, "port");
536 VERIFY_NON_NULL(serverFD, WIFI_SERVER_TAG, "server socket FD");
540 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Invalid input: port is invalid!");
541 return CA_STATUS_INVALID_PARAM;
545 if (CA_FALSE == isSecured)
547 u_mutex_lock(gMutexUnicastServer);
548 if (-1 != gUnicastServerSocketFD)
550 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Unicast Server is Started Already!",
551 CA_SERVER_STARTED_ALREADY);
553 *serverFD = gUnicastServerSocketFD;
554 u_mutex_unlock(gMutexUnicastServer);
555 return CA_SERVER_STARTED_ALREADY;
558 gStopUnicast = false;
559 if (CA_STATUS_OK != CAStartUnicastServer(localAddress, port, forceStart, isSecured,
560 &gUnicastServerSocketFD))
562 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to start unicast server!");
563 gUnicastServerSocketFD = -1;
564 u_mutex_unlock(gMutexUnicastServer);
565 return CA_STATUS_FAILED;
568 *serverFD = gUnicastServerSocketFD;
569 u_mutex_unlock(gMutexUnicastServer);
572 else // Start unicast server for secured communication
574 u_mutex_lock(gMutexSecureUnicastServer);
575 if (-1 != gSecureUnicastServerSocketFD)
577 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Unicast Server is Started Already!",
578 CA_SERVER_STARTED_ALREADY);
580 *serverFD = gSecureUnicastServerSocketFD;
581 u_mutex_unlock(gMutexSecureUnicastServer);
582 return CA_SERVER_STARTED_ALREADY;
585 gStopSecureUnicast = false;
586 if (CA_STATUS_OK != CAStartUnicastServer(localAddress, port, forceStart, isSecured,
587 &gSecureUnicastServerSocketFD))
589 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to start unicast server!");
590 gSecureUnicastServerSocketFD = -1;
591 u_mutex_unlock(gMutexSecureUnicastServer);
592 return CA_STATUS_FAILED;
595 *serverFD = gSecureUnicastServerSocketFD;
596 u_mutex_unlock(gMutexSecureUnicastServer);
599 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
603 CAResult_t CAWiFiStartMulticastServer(const char *localAddress, const char *multicastAddress,
604 const int16_t multicastPort, int32_t *serverFD)
606 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
609 VERIFY_NON_NULL(localAddress, WIFI_SERVER_TAG, "localAddress");
610 VERIFY_NON_NULL(multicastAddress, WIFI_SERVER_TAG, "port");
611 VERIFY_NON_NULL(serverFD, WIFI_SERVER_TAG, "server socket FD");
613 int16_t port = multicastPort;
616 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Invalid input: Multicast port is invalid!");
617 return CA_STATUS_INVALID_PARAM;
620 u_mutex_lock(gMutexMulticastServer);
622 if (gMulticastServerSocketFD != -1)
624 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Multicast Server is already running!");
625 u_mutex_unlock(gMutexMulticastServer);
626 return CA_SERVER_STARTED_ALREADY;
629 CAResult_t ret = CAWiFiCreateSocket(&gMulticastServerSocketFD, multicastAddress, &port, true);
630 if (ret != CA_STATUS_OK)
632 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Failed to create multicast socket");
633 u_mutex_unlock(gMutexMulticastServer);
637 // Add membership to receiving socket (join group)
638 memset(&gMulticastMemberReq, 0, sizeof(struct ip_mreq));
639 gMulticastMemberReq.imr_interface.s_addr = inet_addr(localAddress);
640 inet_aton(multicastAddress, &gMulticastMemberReq.imr_multiaddr);
642 if (-1 == setsockopt(gMulticastServerSocketFD, IPPROTO_IP, IP_ADD_MEMBERSHIP,
643 (char *) &gMulticastMemberReq,
644 sizeof(struct ip_mreq)))
646 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to add to multicast group, Error code: %s\n",
648 close(gMulticastServerSocketFD);
649 gMulticastServerSocketFD = -1;
650 u_mutex_unlock(gMutexMulticastServer);
651 return CA_STATUS_FAILED;
655 * The task to listen to data from multicastcast socket is added to the thread pool.
656 * This is a blocking call is made where we try to receive some data.
657 * We will keep waiting until some data is received.
658 * This task will be terminated when thread pool is freed on stopping the adapters.
659 * Thread context will be freed by thread on exit.
661 CAAdapterReceiveThreadContext_t *ctx = (CAAdapterReceiveThreadContext_t *)
662 OICMalloc(sizeof(CAAdapterReceiveThreadContext_t));
665 OIC_LOG(ERROR, WIFI_SERVER_TAG, "Out of memory!");
666 close(gMulticastServerSocketFD);
667 gMulticastServerSocketFD = -1;
668 return CA_MEMORY_ALLOC_FAILED;
671 ctx->stopFlag = &gStopMulticast;
672 ctx->socket_fd = gMulticastServerSocketFD;
673 ctx->type = CA_MULTICAST_SERVER;
675 gStopMulticast = false;
676 if (CA_STATUS_OK != u_thread_pool_add_task(gThreadPool, CAReceiveHandler, (void *)ctx))
678 OIC_LOG(ERROR, WIFI_SERVER_TAG, "thread_pool_add_task failed!");
680 close(gMulticastServerSocketFD);
681 gMulticastServerSocketFD = -1;
682 gStopMulticast = true;
683 u_mutex_unlock(gMutexMulticastServer);
684 return CA_STATUS_FAILED;
687 *serverFD = gMulticastServerSocketFD;
688 strncpy(gMulticastServerInterface, localAddress, sizeof(gMulticastServerInterface));
689 u_mutex_unlock(gMutexMulticastServer);
691 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
695 CAResult_t CAWiFiStopUnicastServer()
697 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
699 u_mutex_lock(gMutexUnicastServer);
701 CAResult_t ret = CAWiFiCloseSocket(&gUnicastServerSocketFD);
702 u_mutex_unlock(gMutexUnicastServer);
704 OIC_LOG_V(INFO, WIFI_SERVER_TAG, "Unicast server stopped [%d]", ret);
709 CAResult_t CAWiFiStopSecureUnicastServer()
711 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
713 u_mutex_lock(gMutexSecureUnicastServer);
714 gStopSecureUnicast = true;
715 CAResult_t ret = CAWiFiCloseSocket(&gSecureUnicastServerSocketFD);
716 u_mutex_unlock(gMutexSecureUnicastServer);
718 OIC_LOG_V(INFO, WIFI_SERVER_TAG, "Secured unicast server stopped [%d]", ret);
723 CAResult_t CAWiFiStopMulticastServer(void)
725 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
727 u_mutex_lock(gMutexMulticastServer);
729 if (gMulticastServerSocketFD == -1)
731 OIC_LOG(INFO, WIFI_SERVER_TAG, "Multicast server is not yet started");
732 u_mutex_unlock(gMutexMulticastServer);
733 return CA_SERVER_NOT_STARTED;
736 gStopMulticast = true;
738 // leave the group after you are done
739 if (-1 == setsockopt(gMulticastServerSocketFD, IPPROTO_IP, IP_DROP_MEMBERSHIP,
740 (char *)&gMulticastMemberReq,
741 sizeof(struct ip_mreq)))
743 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed to leave multicast group, Error code: %s\n",
747 CAResult_t ret = CAWiFiCloseSocket(&gMulticastServerSocketFD);
748 u_mutex_unlock(gMutexMulticastServer);
750 OIC_LOG_V(INFO, WIFI_SERVER_TAG, "Multicast server stopped [%d]", ret);
754 CAResult_t CAWiFiGetUnicastServerInfo(const CABool_t isSecured, char **ipAddress,
755 int16_t *port, int32_t *serverFD)
757 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
760 VERIFY_NON_NULL(ipAddress, WIFI_SERVER_TAG, "IP address");
761 VERIFY_NON_NULL(port, WIFI_SERVER_TAG, "Port");
762 VERIFY_NON_NULL(serverFD, WIFI_SERVER_TAG, "Server ID");
764 struct sockaddr_in sockAddr;
765 socklen_t len = sizeof(struct sockaddr_in);
766 if (-1 == getsockname(gUnicastServerSocketFD, (struct sockaddr *)&sockAddr, &len))
768 OIC_LOG_V(ERROR, WIFI_SERVER_TAG, "Failed in getsockname [%s]!", strerror(errno));
769 return CA_STATUS_FAILED;
773 const char *serverAddress = inet_ntoa(sockAddr.sin_addr);
774 *ipAddress = (serverAddress) ? strndup(serverAddress, strlen(serverAddress)) : NULL;
775 *port = ntohs(sockAddr.sin_port);
777 *serverFD = (CA_TRUE == isSecured) ? gSecureUnicastServerSocketFD : gUnicastServerSocketFD;
779 *serverFD = gUnicastServerSocketFD;
781 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "OUT");
785 void CAWiFiSetPacketReceiveCallback(CAWiFiPacketReceivedCallback callback)
787 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
789 gPacketReceivedCallback = callback;
792 void CAWiFiSetExceptionCallback(CAWiFiExceptionCallback callback)
794 OIC_LOG(DEBUG, WIFI_SERVER_TAG, "IN");
796 gExceptionCallback = callback;