diff options
author | Lars Heer <l.heer@gmx.de> | 2013-09-18 05:50:03 +0200 |
---|---|---|
committer | Lars Heer <l.heer@gmx.de> | 2013-09-18 05:50:03 +0200 |
commit | ccf6e0f9c6b0481ed13e0f4794e3fbead750f385 (patch) | |
tree | ed86efb54f7ee41edfba5c89ca519b5fd10aa0d5 /mcast/common/mcast.c | |
download | vdr-plugin-mcli-ccf6e0f9c6b0481ed13e0f4794e3fbead750f385.tar.gz vdr-plugin-mcli-ccf6e0f9c6b0481ed13e0f4794e3fbead750f385.tar.bz2 |
added vdr-plugin-mcli-0.0.1+svn20120927
Diffstat (limited to 'mcast/common/mcast.c')
-rw-r--r-- | mcast/common/mcast.c | 674 |
1 files changed, 674 insertions, 0 deletions
diff --git a/mcast/common/mcast.c b/mcast/common/mcast.c new file mode 100644 index 0000000..41991cf --- /dev/null +++ b/mcast/common/mcast.c @@ -0,0 +1,674 @@ +/* + * (c) BayCom GmbH, http://www.baycom.de, info@baycom.de + * + * See the COPYING file for copyright information and + * how to reach the author. + * + * modified by Reel Multimedia, http://www.reel-multimedia.com, info@reel-multimedia.com + * 01042010 DL: use a single thread for reading from network layer (uses less resources) + * + */ + +#include "headers.h" +//---------------------------------------------------------------------------------------------------------------------------------- +STATIC int udp_ipv6_is_multicast_address (const struct sockaddr *addr) +{ +#ifdef IPV4 + if (addr->sa_family == AF_INET) + return IN_MULTICAST (ntohl (((struct sockaddr_in *) addr)->sin_addr.s_addr)); +#endif + if (addr->sa_family == AF_INET6) + return IN6_IS_ADDR_MULTICAST (&((struct sockaddr_in6 *) addr)->sin6_addr); + return -1; +} + +//--------------------------------------------------------------------------------------------------------------------------------- +STATIC int udp_ipv6_set_multicast_ttl (SOCKET sockfd, int mcastTTL, struct sockaddr *addr) +{ +#ifdef IPV4 + if (addr->sa_family == AF_INET) { + if (setsockopt (sockfd, IPPROTO_IP, IP_MULTICAST_TTL, &mcastTTL, sizeof (mcastTTL)) < 0) { + perror ("setsockopt(IP_MULTICAST_TTL)"); + return -1; + } + } +#endif + if (addr->sa_family == AF_INET6) { + if (setsockopt (sockfd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (_SOTYPE)&mcastTTL, sizeof (mcastTTL)) < 0) { + perror ("setsockopt(IPV6_MULTICAST_HOPS)"); + return -1; + } + } + return 0; +} + +//--------------------------------------------------------------------------------------------------------------------------------- +int udp_ipv6_join_multicast_group (SOCKET sockfd, int iface, struct sockaddr *addr) +{ +#ifdef IPV4 + if (addr->sa_family == AF_INET) { + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *) addr)->sin_addr.s_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt (sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, (const void *) &mreq, sizeof (mreq)) < 0) { + perror ("setsockopt(IP_ADD_MEMBERSHIP)"); + return -1; + } + } +#endif + if (addr->sa_family == AF_INET6) { + struct ipv6_mreq mreq6; + memcpy (&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *) addr)->sin6_addr), sizeof (struct in6_addr)); + mreq6.ipv6mr_interface = iface; + if (setsockopt (sockfd, IPPROTO_IPV6, IPV6_ADD_MEMBERSHIP, (_SOTYPE)&mreq6, sizeof (mreq6)) < 0) { + perror ("setsockopt(IPV6_ADD_MEMBERSHIP)"); + return -1; + } + } + return 0; +} + +//--------------------------------------------------------------------------------------------------------------------------------- +int udp_ipv6_leave_multicast_group (SOCKET sockfd, int iface, struct sockaddr *addr) +{ +#ifdef IPV4 + if (addr->sa_family == AF_INET) { + struct ip_mreq mreq; + mreq.imr_multiaddr.s_addr = ((struct sockaddr_in *) addr)->sin_addr.s_addr; + mreq.imr_interface.s_addr = INADDR_ANY; + if (setsockopt (sockfd, IPPROTO_IP, IP_DROP_MEMBERSHIP, (const void *) &mreq, sizeof (mreq)) < 0) { + perror ("setsockopt(IP_DROP_MEMBERSHIP)"); + return -1; + } + } +#endif + if (addr->sa_family == AF_INET6) { + struct ipv6_mreq mreq6; + memcpy (&mreq6.ipv6mr_multiaddr, &(((struct sockaddr_in6 *) addr)->sin6_addr), sizeof (struct in6_addr)); + mreq6.ipv6mr_interface = iface; + if (setsockopt (sockfd, IPPROTO_IPV6, IPV6_DROP_MEMBERSHIP, (_SOTYPE)&mreq6, sizeof (mreq6)) < 0) { + perror ("setsockopt(IPV6_DROP_MEMBERSHIP)"); + return -1; + } + } + return 0; +} + +//----------------------------------------------------------------------------------------------------------------------------------- +STATIC int sockfd_to_family (SOCKET sockfd) +{ + struct sockaddr_storage ss; + socklen_t len; + + len = sizeof (ss); + if (getsockname (sockfd, (SA *) & ss, &len) < 0) + return (-1); + return (ss.ss_family); +} + +/* end sockfd_to_family */ +//---------------------------------------------------------------------------------------------------------------------------------- +int mcast_set_if (SOCKET sockfd, const char *ifname, u_int ifindex) +{ + switch (sockfd_to_family (sockfd)) { +#ifdef IPV4 + case AF_INET:{ + struct in_addr inaddr; + struct ifreq ifreq; + + if (ifindex > 0) { + if (if_indextoname (ifindex, ifreq.ifr_name) == NULL) { + errno = ENXIO; /* i/f index not found */ + return (-1); + } + goto doioctl; + } else if (ifname != NULL) { + memset(&ifreq, 0, sizeof(struct ifreq)); + strncpy (ifreq.ifr_name, ifname, IFNAMSIZ-1); + doioctl: + if (ioctl (sockfd, SIOCGIFADDR, &ifreq) < 0) + return (-1); + memcpy (&inaddr, &((struct sockaddr_in *) &ifreq.ifr_addr)->sin_addr, sizeof (struct in_addr)); + } else + inaddr.s_addr = htonl (INADDR_ANY); /* remove prev. set default */ + + return (setsockopt (sockfd, IPPROTO_IP, IP_MULTICAST_IF, &inaddr, sizeof (struct in_addr))); + } +#endif + case AF_INET6:{ + u_int idx; +// printf("Changing interface IPV6...\n"); + if ((idx = ifindex) == 0) { + if (ifname == NULL) { + errno = EINVAL; /* must supply either index or name */ + return (-1); + } + if ((idx = if_nametoindex (ifname)) == 0) { + errno = ENXIO; /* i/f name not found */ + return (-1); + } + } + return (setsockopt (sockfd, IPPROTO_IPV6, IPV6_MULTICAST_IF, (_SOTYPE)&idx, sizeof (idx))); + } + + default: +// errno = EAFNOSUPPORT; + return (-1); + } +} + +//-------------------------------------------------------------------------------------------------------------------------------------------- +UDPContext *server_udp_open (const struct in6_addr *mcg, int port, const char *ifname) +{ + UDPContext *s; + int sendfd; + int n; + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); + + s = (UDPContext *) calloc (1, sizeof (UDPContext)); + if (!s) { + err ("Cannot allocate memory !\n"); + goto error; + } + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &s->dest_addr; + + addr->sin6_addr=*mcg;; + addr->sin6_family = AF_INET6; + addr->sin6_port = htons (port); + s->dest_addr_len = sizeof (struct sockaddr_in6); + + sendfd = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (sendfd < 0) { + err ("cannot get socket\n"); + } + + s->dest_addr_len = sizeof (struct sockaddr_in6); + + if ((udp_ipv6_is_multicast_address ((struct sockaddr *) &s->dest_addr))) { + if (ifname && strlen (ifname) && (mcast_set_if (sendfd, ifname, 0) < 0)) { + warn ("mcast_set_if error\n"); + goto error; + } + if (udp_ipv6_set_multicast_ttl (sendfd, MCAST_TTL, (struct sockaddr *) &s->dest_addr) < 0) { + warn ("udp_ipv6_set_multicast_ttl"); + } + } + + n = UDP_TX_BUF_SIZE; + if (setsockopt (sendfd, SOL_SOCKET, SO_SNDBUF, (_SOTYPE)&n, sizeof (n)) < 0) { + warn ("setsockopt sndbuf"); + } + s->is_multicast = 0; //server + s->udp_fd = sendfd; + s->local_port = port; + + dbg ("Multicast streamer initialized successfully ! \n"); + + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + return s; + error: + err ("Cannot init udp_server !\n"); + if (s) { + free (s); + } + + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + return NULL; +} + +UDPContext *server_udp_open_host (const char *host, int port, const char *ifname) +{ + struct in6_addr addr; + + inet_pton (AF_INET6, host, &addr); + + return server_udp_open (&addr, port, ifname); +} + +//-------------------------------------------------------------------------------------------------------------------------------------------- +UDPContext *client_udp_open (const struct in6_addr *mcg, int port, const char *ifname) +{ + UDPContext *s; + int recvfd; + int n; + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); + + s = (UDPContext *) calloc (1, sizeof (UDPContext)); + if (!s) { + err ("Cannot allocate memory !\n"); + goto error; + } + + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &s->dest_addr; +#ifndef WIN32 + addr->sin6_addr=*mcg; +#else + struct in6_addr any; + memset(&any,0,sizeof(any)); + addr->sin6_addr=any; +#endif + addr->sin6_family = AF_INET6; + addr->sin6_port = htons (port); + s->dest_addr_len = sizeof (struct sockaddr_in6); + + recvfd = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (recvfd < 0) { + err ("cannot get socket\n"); + } +#ifdef WIN32 +# ifndef IPV6_PROTECTION_LEVEL +# define IPV6_PROTECTION_LEVEL 23 +# endif + n = 10 /*PROTECTION_LEVEL_UNRESTRICTED*/; + if(setsockopt( recvfd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (_SOTYPE)&n, sizeof(n) ) < 0 ) { + warn ("setsockopt IPV6_PROTECTION_LEVEL\n"); + } +#endif + n = 1; + if (setsockopt (recvfd, SOL_SOCKET, SO_REUSEADDR, (_SOTYPE)&n, sizeof (n)) < 0) { + warn ("setsockopt REUSEADDR\n"); + } + +#if ! (defined WIN32 || defined APPLE) + if (ifname && strlen (ifname) && setsockopt (recvfd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen (ifname) + 1)) { + dbg ("setsockopt SO_BINDTODEVICE %s failed\n", ifname); + } +#endif + if (bind (recvfd, (struct sockaddr *) &s->dest_addr, s->dest_addr_len) < 0) { + warn ("bind failed\n"); + goto error; + } +#ifdef WIN32 + addr->sin6_addr=*mcg; +#endif + if (udp_ipv6_is_multicast_address ((struct sockaddr *) &s->dest_addr)) { +#if 0 + if (ifname && strlen (ifname) && (mcast_set_if (recvfd, ifname, 0) < 0)) { + warn ("mcast_set_if error \n"); + goto error; + } +#endif + if (ifname) { + if ((s->idx = if_nametoindex (ifname)) == 0) { + s->idx = 0; + } else { + dbg("Selecting interface %s (%d)", ifname, s->idx); + } + } else { + s->idx = 0; + } + + if (udp_ipv6_join_multicast_group (recvfd, s->idx, (struct sockaddr *) &s->dest_addr) < 0) { + warn ("Cannot join multicast group !\n"); + goto error; + } + s->is_multicast = 1; + } + + n = UDP_RX_BUF_SIZE; + if (setsockopt (recvfd, SOL_SOCKET, SO_RCVBUF, (_SOTYPE)&n, sizeof (n)) < 0) { + warn ("setsockopt rcvbuf"); + goto error; + } + + s->udp_fd = recvfd; + s->local_port = port; + + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + + return s; + error: + warn ("socket error !\n"); + if (s) { + free (s); + } + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + return NULL; +} + +UDPContext *client_udp_open_host (const char *host, int port, const char *ifname) +{ + struct in6_addr addr; + + inet_pton (AF_INET6, host, &addr); + + return client_udp_open (&addr, port, ifname); +} + +//-------------------------------------------------------------------------------------------------------------------------------------------- +int udp_read (UDPContext * s, uint8_t * buf, int size, int timeout, struct sockaddr_storage *from) +{ + socklen_t from_len = sizeof (struct sockaddr_storage); + struct sockaddr_storage from_local; + + if(!from) { + from=&from_local; + } + + struct pollfd p; + p.fd = s->udp_fd; + p.events = POLLIN; + + if(poll(&p,1,(timeout+999)>>10)>0) { + return recvfrom (s->udp_fd, (char *)buf, size, 0, (struct sockaddr *) from, &from_len); + } + return -1; +} + +//-------------------------------------------------------------------------------------------------------------------------------------------------- +int udp_write (UDPContext * s, uint8_t * buf, int size) +{ + int ret; + + for (;;) { + ret = sendto (s->udp_fd, (char *) buf, size, 0, (struct sockaddr *) &s->dest_addr, s->dest_addr_len); + + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else { + break; + } + } + return size; +} + +//---------------------------------------------------------------------------------------------------------------------------------------------------- +int udp_close (UDPContext * s) +{ + if (s->is_multicast) + udp_ipv6_leave_multicast_group (s->udp_fd, s->idx, (struct sockaddr *) &s->dest_addr); + + closesocket (s->udp_fd); + free (s); + + return 0; +} + +//-------------------------------------------------------------------------------------------------------------------------------------------- + +#ifndef MULTI_THREAD_RECEIVER + +#define MAX_BUFF_SIZE 0x10000 +#define MAX_CON_LIST 128 +UDPContext *gConList[MAX_CON_LIST]; +pthread_mutex_t gConListLock = PTHREAD_MUTEX_INITIALIZER; +static int gConListInit=0; +static int gConListModified; +static UDPContext *gConChain; + +STATIC void client_upd_cleanup (void *arg) { + if(!gConListInit) return; + pthread_mutex_lock(&gConListLock); + memset(&gConList, 0, sizeof(gConList)); + gConListInit=0; + pthread_mutex_unlock(&gConListLock); +} // client_upd_cleanup + +void *client_upd_process(void *arg) { +#ifdef RT +#if 1 + if (setpriority (PRIO_PROCESS, 0, -15) == -1) +#else + if (pthread_setschedprio (p->recv_ts_thread, -15)) +#endif + { + dbg ("Cannot raise priority to -15\n"); + } +#endif + unsigned char buff[MAX_BUFF_SIZE]; + socklen_t from_len = sizeof (struct sockaddr_storage); + struct sockaddr_storage from_local; + struct pollfd fds[MAX_CON_LIST]; + + pthread_cleanup_push (client_upd_cleanup, 0); + int max_fd=0; + while(1) { + UDPContext *e; + pthread_mutex_lock(&gConListLock); + + if(gConListModified) { + gConListModified=0; + max_fd=0; + for(e=gConChain;e;e=e->next) { + fds[max_fd].fd = e->udp_fd; + fds[max_fd].events = POLLIN; + fds[max_fd].revents = 0; + e->pfd = &fds[max_fd]; + max_fd++; + } // for + } // if + pthread_mutex_unlock(&gConListLock); + int rs = poll(fds, max_fd, 1000); + if(rs>0) { + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); + pthread_mutex_lock(&gConListLock); + for(e=gConChain;e;e=e->next) { + if(e->pfd && (e->pfd->revents & POLLIN)) { + if(e->cb) { + int ret = recvfrom (e->udp_fd, (char *)buff, MAX_BUFF_SIZE, 0, 0, 0/*(struct sockaddr *) &from_local, &from_len*/); + if(ret>0) + e->cb(buff, ret, e->arg); + } else if(e->buff && !e->bufflen) { + pthread_mutex_lock(&e->bufflock); + int ret = recvfrom (e->udp_fd, (char *)e->buff, e->buffmax, 0, (struct sockaddr *) &from_local, &from_len); + if(ret>0) + e->bufflen = ret; + pthread_mutex_unlock(&e->bufflock); + } // if + } // if + } // for + pthread_mutex_unlock(&gConListLock); + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + } // if + pthread_testcancel(); + } // while + pthread_cleanup_pop (1); + return NULL; +} + +static int client_upd_init() { + pthread_mutex_lock(&gConListLock); + if(gConListInit) { + pthread_mutex_unlock(&gConListLock); + return 1; + } // if + memset(&gConList, 0, sizeof(gConList)); + gConListModified = 0; + gConChain = NULL; + pthread_t client_upd_thread; + if(0==pthread_create (&client_upd_thread, NULL, client_upd_process, 0)) { + gConListInit = 1; + pthread_detach(client_upd_thread); + } // if + pthread_mutex_unlock(&gConListLock); + return gConListInit; +} // client_upd_init + +UDPContext *client_udp_open_buff (const struct in6_addr *mcg, int port, const char *ifname, int buff_size) { + UDPContext *ret = client_udp_open_cb (mcg, port, ifname, 0, 0); + if(ret) { + ret->buff = (unsigned char *)malloc(buff_size); + ret->buffmax = buff_size; + ret->bufflen = 0; + if (!ret->buff) { + err ("client_udp_open_buff: out of memory\n"); + } + } // if + return ret; +} // client_udp_open_buff + +UDPContext *client_udp_open_cb (const struct in6_addr *mcg, int port, const char *ifname, client_udp_cb cb, void *arg) +{ + if(!client_upd_init()) return NULL; + + UDPContext *s; + int recvfd = -1; + int n; + + pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, NULL); + + s = (UDPContext *) calloc (1, sizeof (UDPContext)); + if (!s) { + err ("Cannot allocate memory !\n"); + goto error; + } + + struct sockaddr_in6 *addr = (struct sockaddr_in6 *) &s->dest_addr; +#ifndef WIN32 + addr->sin6_addr=*mcg; +#else + struct in6_addr any=IN6ADDR_ANY_INIT; + addr->sin6_addr=any; +#endif + addr->sin6_family = AF_INET6; + addr->sin6_port = htons (port); + s->dest_addr_len = sizeof (struct sockaddr_in6); + + recvfd = socket (PF_INET6, SOCK_DGRAM, IPPROTO_UDP); + if (recvfd < 0) { + err ("cannot get socket\n"); + } +#ifdef WIN32 +# ifndef IPV6_PROTECTION_LEVEL +# define IPV6_PROTECTION_LEVEL 23 +# endif + n = 10 /*PROTECTION_LEVEL_UNRESTRICTED*/; + if(setsockopt( recvfd, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL, (_SOTYPE)&n, sizeof(n) ) < 0 ) { + warn ("setsockopt IPV6_PROTECTION_LEVEL\n"); + } +#endif + n = 1; + if (setsockopt (recvfd, SOL_SOCKET, SO_REUSEADDR, (_SOTYPE)&n, sizeof (n)) < 0) { + warn ("setsockopt REUSEADDR\n"); + } + +#if ! (defined WIN32 || defined APPLE) + if (ifname && strlen (ifname) && setsockopt (recvfd, SOL_SOCKET, SO_BINDTODEVICE, ifname, strlen (ifname) + 1)) { + dbg ("setsockopt SO_BINDTODEVICE %s failed\n", ifname); + } +#endif + if (bind (recvfd, (struct sockaddr *) &s->dest_addr, s->dest_addr_len) < 0) { + warn ("bind failed\n"); + goto error; + } +#ifdef WIN32 + addr->sin6_addr=*mcg; +#endif + if (udp_ipv6_is_multicast_address ((struct sockaddr *) &s->dest_addr)) { +#if 0 + if (ifname && strlen (ifname) && (mcast_set_if (recvfd, ifname, 0) < 0)) { + warn ("mcast_set_if error \n"); + goto error; + } +#endif + if (ifname) { + if ((s->idx = if_nametoindex (ifname)) == 0) { + s->idx = 0; + } else { + dbg("Selecting interface %s (%d)", ifname, s->idx); + } + } else { + s->idx = 0; + } + + if (udp_ipv6_join_multicast_group (recvfd, s->idx, (struct sockaddr *) &s->dest_addr) < 0) { + warn ("Cannot join multicast group !\n"); + goto error; + } + s->is_multicast = 1; + } + + n = cb ? UDP_PID_BUF_SIZE : UDP_RX_BUF_SIZE; + if (setsockopt (recvfd, SOL_SOCKET, SO_RCVBUF, (_SOTYPE)&n, sizeof (n)) < 0) { + warn ("setsockopt rcvbuf"); + goto error; + } + + s->udp_fd = recvfd; + s->local_port = port; + + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + + s->cb = cb; + s->arg = arg; + pthread_mutex_init(&s->bufflock, NULL); + int i; + pthread_mutex_lock(&gConListLock); + for(i=0;i<MAX_CON_LIST;i++) { + if(!gConList[i]) { + gConList[i]=s; + gConListModified=1; + s->next=gConChain; + gConChain=s; + break; + } // if + } // for + pthread_mutex_unlock(&gConListLock); + if(i>=MAX_CON_LIST) + warn("---------------------------------------------No slot found!\n"); + + return s; + error: + warn ("socket error !\n"); + if (s) { + free (s); + } + pthread_setcancelstate (PTHREAD_CANCEL_ENABLE, NULL); + return NULL; +} + +UDPContext *client_udp_open_host_buff (const char *host, int port, const char *ifname, int buff_size) +{ + struct in6_addr addr; + + inet_pton (AF_INET6, host, &addr); + + return client_udp_open_buff (&addr, port, ifname, buff_size); +} + +//-------------------------------------------------------------------------------------------------------------------------------------------- +int udp_read_buff (UDPContext * s, uint8_t * buf, int size, int timeout, struct sockaddr_storage *from) +{ + pthread_mutex_lock(&s->bufflock); + int ret = s->bufflen>size ? size : s->bufflen; + if(ret>0) { + memcpy(buf, s->buff, ret); + s->bufflen-=ret; + } + pthread_mutex_unlock(&s->bufflock); + return ret; +} +//---------------------------------------------------------------------------------------------------------------------------------------------------- +int udp_close_buff (UDPContext * s) +{ + int i; + pthread_mutex_lock(&gConListLock); + for(i=0;i<MAX_CON_LIST;i++) { + if(gConList[i] == s) { + gConList[i]=0; + gConListModified=1; + UDPContext **e; + for(e=&gConChain;*e;e=&(*e)->next) { + if(*e == s) { + *e=(*e)->next; + break; + } + } + break; + } + } + pthread_mutex_unlock(&gConListLock); + if (s->is_multicast) + udp_ipv6_leave_multicast_group (s->udp_fd, s->idx, (struct sockaddr *) &s->dest_addr); + + closesocket (s->udp_fd); + free(s->buff); + pthread_mutex_destroy(&s->bufflock); + free (s); + + return 0; +} +#endif |