/* coap_io.c -- Default network I/O functions for libcoap * * Copyright (C) 2012,2014,2016-2019 Olaf Bergmann and others * * This file is part of the CoAP library libcoap. Please see * README for terms of use. */ #include "coap_config.h" #ifdef HAVE_STDIO_H # include #endif #ifdef HAVE_SYS_SELECT_H # include #endif #ifdef HAVE_SYS_SOCKET_H # include # define OPTVAL_T(t) (t) # define OPTVAL_GT(t) (t) #endif #ifdef HAVE_SYS_IOCTL_H #include #endif #ifdef HAVE_NETINET_IN_H # include #endif #ifdef HAVE_WS2TCPIP_H #include # define OPTVAL_T(t) (const char*)(t) # define OPTVAL_GT(t) (char*)(t) # undef CMSG_DATA # define CMSG_DATA WSA_CMSG_DATA #endif #ifdef HAVE_SYS_UIO_H # include #endif #ifdef HAVE_UNISTD_H # include #endif #include #ifdef WITH_CONTIKI # include "uip.h" #endif #include "libcoap.h" #include "coap_debug.h" #include "mem.h" #include "net.h" #include "coap_io.h" #include "pdu.h" #include "utlist.h" #include "resource.h" #if !defined(WITH_CONTIKI) /* define generic PKTINFO for IPv4 */ #if defined(IP_PKTINFO) # define GEN_IP_PKTINFO IP_PKTINFO #elif defined(IP_RECVDSTADDR) # define GEN_IP_PKTINFO IP_RECVDSTADDR #else # error "Need IP_PKTINFO or IP_RECVDSTADDR to request ancillary data from OS." #endif /* IP_PKTINFO */ /* define generic KTINFO for IPv6 */ #ifdef IPV6_RECVPKTINFO # define GEN_IPV6_PKTINFO IPV6_RECVPKTINFO #elif defined(IPV6_PKTINFO) # define GEN_IPV6_PKTINFO IPV6_PKTINFO #else # error "Need IPV6_PKTINFO or IPV6_RECVPKTINFO to request ancillary data from OS." #endif /* IPV6_RECVPKTINFO */ #endif void coap_free_endpoint(coap_endpoint_t *ep); #ifdef WITH_CONTIKI static int ep_initialized = 0; struct coap_endpoint_t * coap_malloc_endpoint() { static struct coap_endpoint_t ep; if (ep_initialized) { return NULL; } else { ep_initialized = 1; return &ep; } } void coap_mfree_endpoint(struct coap_endpoint_t *ep) { ep_initialized = 0; coap_session_mfree(&ep->hello); } int coap_socket_bind_udp(coap_socket_t *sock, const coap_address_t *listen_addr, coap_address_t *bound_addr) { sock->conn = udp_new(NULL, 0, NULL); if (!sock->conn) { coap_log(LOG_WARNING, "coap_socket_bind_udp"); return 0; } coap_address_init(bound_addr); uip_ipaddr_copy(&bound_addr->addr, &listen_addr->addr); bound_addr->port = listen_addr->port; udp_bind((struct uip_udp_conn *)sock->conn, bound_addr->port); return 1; } int coap_socket_connect_udp(coap_socket_t *sock, const coap_address_t *local_if, const coap_address_t *server, int default_port, coap_address_t *local_addr, coap_address_t *remote_addr) { return 0; } int coap_socket_connect_tcp1(coap_socket_t *sock, const coap_address_t *local_if, const coap_address_t *server, int default_port, coap_address_t *local_addr, coap_address_t *remote_addr) { return 0; } int coap_socket_connect_tcp2(coap_socket_t *sock, coap_address_t *local_addr, coap_address_t *remote_addr) { return 0; } int coap_socket_bind_tcp(coap_socket_t *sock, const coap_address_t *listen_addr, coap_address_t *bound_addr) { return 0; } int coap_socket_accept_tcp(coap_socket_t *server, coap_socket_t *new_client, coap_address_t *local_addr, coap_address_t *remote_addr) { return 0; } ssize_t coap_socket_write(coap_socket_t *sock, const uint8_t *data, size_t data_len) { return -1; } ssize_t coap_socket_read(coap_socket_t *sock, uint8_t *data, size_t data_len) { return -1; } void coap_socket_close(coap_socket_t *sock) { if (sock->conn) uip_udp_remove((struct uip_udp_conn *)sock->conn); sock->flags = COAP_SOCKET_EMPTY; } #else static const char *coap_socket_format_errno( int error ); struct coap_endpoint_t * coap_malloc_endpoint(void) { return (struct coap_endpoint_t *)coap_malloc_type(COAP_ENDPOINT, sizeof(struct coap_endpoint_t)); } void coap_mfree_endpoint(struct coap_endpoint_t *ep) { coap_session_mfree(&ep->hello); coap_free_type(COAP_ENDPOINT, ep); } int coap_socket_bind_udp(coap_socket_t *sock, const coap_address_t *listen_addr, coap_address_t *bound_addr) { int on = 1, off = 0; #ifdef _WIN32 u_long u_on = 1; #endif sock->fd = socket(listen_addr->addr.sa.sa_family, SOCK_DGRAM, 0); if (sock->fd == COAP_INVALID_SOCKET) { coap_log(LOG_WARNING, "coap_socket_bind_udp: socket: %s\n", coap_socket_strerror()); goto error; } #ifdef _WIN32 if (ioctlsocket(sock->fd, FIONBIO, &u_on) == COAP_SOCKET_ERROR) { #else if (ioctl(sock->fd, FIONBIO, &on) == COAP_SOCKET_ERROR) { #endif coap_log(LOG_WARNING, "coap_socket_bind_udp: ioctl FIONBIO: %s\n", coap_socket_strerror()); } if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_bind_udp: setsockopt SO_REUSEADDR: %s\n", coap_socket_strerror()); switch (listen_addr->addr.sa.sa_family) { case AF_INET: if (setsockopt(sock->fd, IPPROTO_IP, GEN_IP_PKTINFO, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_ALERT, "coap_socket_bind_udp: setsockopt IP_PKTINFO: %s\n", coap_socket_strerror()); break; case AF_INET6: /* Configure the socket as dual-stacked */ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, OPTVAL_T(&off), sizeof(off)) == COAP_SOCKET_ERROR) coap_log(LOG_ALERT, "coap_socket_bind_udp: setsockopt IPV6_V6ONLY: %s\n", coap_socket_strerror()); if (setsockopt(sock->fd, IPPROTO_IPV6, GEN_IPV6_PKTINFO, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_ALERT, "coap_socket_bind_udp: setsockopt IPV6_PKTINFO: %s\n", coap_socket_strerror()); setsockopt(sock->fd, IPPROTO_IP, GEN_IP_PKTINFO, OPTVAL_T(&on), sizeof(on)); /* ignore error, because the likely cause is that IPv4 is disabled at the os level */ break; default: coap_log(LOG_ALERT, "coap_socket_bind_udp: unsupported sa_family\n"); break; } if (bind(sock->fd, &listen_addr->addr.sa, listen_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_bind_udp: bind: %s\n", coap_socket_strerror()); goto error; } bound_addr->size = (socklen_t)sizeof(*bound_addr); if (getsockname(sock->fd, &bound_addr->addr.sa, &bound_addr->size) < 0) { coap_log(LOG_WARNING, "coap_socket_bind_udp: getsockname: %s\n", coap_socket_strerror()); goto error; } return 1; error: coap_socket_close(sock); return 0; } int coap_socket_connect_tcp1(coap_socket_t *sock, const coap_address_t *local_if, const coap_address_t *server, int default_port, coap_address_t *local_addr, coap_address_t *remote_addr) { int on = 1, off = 0; #ifdef _WIN32 u_long u_on = 1; #endif coap_address_t connect_addr; coap_address_copy( &connect_addr, server ); sock->flags &= ~COAP_SOCKET_CONNECTED; sock->fd = socket(server->addr.sa.sa_family, SOCK_STREAM, 0); if (sock->fd == COAP_INVALID_SOCKET) { coap_log(LOG_WARNING, "coap_socket_connect_tcp1: socket: %s\n", coap_socket_strerror()); goto error; } #ifdef _WIN32 if (ioctlsocket(sock->fd, FIONBIO, &u_on) == COAP_SOCKET_ERROR) { #else if (ioctl(sock->fd, FIONBIO, &on) == COAP_SOCKET_ERROR) { #endif coap_log(LOG_WARNING, "coap_socket_connect_tcp1: ioctl FIONBIO: %s\n", coap_socket_strerror()); } switch (server->addr.sa.sa_family) { case AF_INET: if (connect_addr.addr.sin.sin_port == 0) connect_addr.addr.sin.sin_port = htons(default_port); break; case AF_INET6: if (connect_addr.addr.sin6.sin6_port == 0) connect_addr.addr.sin6.sin6_port = htons(default_port); /* Configure the socket as dual-stacked */ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, OPTVAL_T(&off), sizeof(off)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_connect_tcp1: setsockopt IPV6_V6ONLY: %s\n", coap_socket_strerror()); break; default: coap_log(LOG_ALERT, "coap_socket_connect_tcp1: unsupported sa_family\n"); break; } if (local_if && local_if->addr.sa.sa_family) { coap_address_copy(local_addr, local_if); if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_connect_tcp1: setsockopt SO_REUSEADDR: %s\n", coap_socket_strerror()); if (bind(sock->fd, &local_if->addr.sa, local_if->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_tcp1: bind: %s\n", coap_socket_strerror()); goto error; } } else { local_addr->addr.sa.sa_family = server->addr.sa.sa_family; } if (connect(sock->fd, &connect_addr.addr.sa, connect_addr.size) == COAP_SOCKET_ERROR) { #ifdef _WIN32 if (WSAGetLastError() == WSAEWOULDBLOCK) { #else if (errno == EINPROGRESS) { #endif /* * COAP_SOCKET_CONNECTED needs to be set here as there will be reads/writes * by underlying TLS libraries during connect() and we do not want to * assert() in coap_read_session() or coap_write_session() when called by coap_read() */ sock->flags |= COAP_SOCKET_WANT_CONNECT | COAP_SOCKET_CONNECTED; return 1; } coap_log(LOG_WARNING, "coap_socket_connect_tcp1: connect: %s\n", coap_socket_strerror()); goto error; } if (getsockname(sock->fd, &local_addr->addr.sa, &local_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_tcp1: getsockname: %s\n", coap_socket_strerror()); } if (getpeername(sock->fd, &remote_addr->addr.sa, &remote_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_tcp1: getpeername: %s\n", coap_socket_strerror()); } sock->flags |= COAP_SOCKET_CONNECTED; return 1; error: coap_socket_close(sock); return 0; } int coap_socket_connect_tcp2(coap_socket_t *sock, coap_address_t *local_addr, coap_address_t *remote_addr) { int error = 0; #ifdef _WIN32 int optlen = (int)sizeof( error ); #else socklen_t optlen = (socklen_t)sizeof( error ); #endif sock->flags &= ~(COAP_SOCKET_WANT_CONNECT | COAP_SOCKET_CAN_CONNECT); if (getsockopt(sock->fd, SOL_SOCKET, SO_ERROR, OPTVAL_GT(&error), &optlen) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_finish_connect_tcp: getsockopt: %s\n", coap_socket_strerror()); } if (error) { coap_log(LOG_WARNING, "coap_socket_finish_connect_tcp: connect failed: %s\n", coap_socket_format_errno(error)); coap_socket_close(sock); return 0; } if (getsockname(sock->fd, &local_addr->addr.sa, &local_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_tcp: getsockname: %s\n", coap_socket_strerror()); } if (getpeername(sock->fd, &remote_addr->addr.sa, &remote_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_tcp: getpeername: %s\n", coap_socket_strerror()); } return 1; } int coap_socket_bind_tcp(coap_socket_t *sock, const coap_address_t *listen_addr, coap_address_t *bound_addr) { int on = 1, off = 0; #ifdef _WIN32 u_long u_on = 1; #endif sock->fd = socket(listen_addr->addr.sa.sa_family, SOCK_STREAM, 0); if (sock->fd == COAP_INVALID_SOCKET) { coap_log(LOG_WARNING, "coap_socket_bind_tcp: socket: %s\n", coap_socket_strerror()); goto error; } #ifdef _WIN32 if (ioctlsocket(sock->fd, FIONBIO, &u_on) == COAP_SOCKET_ERROR) { #else if (ioctl(sock->fd, FIONBIO, &on) == COAP_SOCKET_ERROR) { #endif coap_log(LOG_WARNING, "coap_socket_bind_tcp: ioctl FIONBIO: %s\n", coap_socket_strerror()); } if (setsockopt (sock->fd, SOL_SOCKET, SO_KEEPALIVE, OPTVAL_T(&on), sizeof (on)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_bind_tcp: setsockopt SO_KEEPALIVE: %s\n", coap_socket_strerror()); if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_bind_tcp: setsockopt SO_REUSEADDR: %s\n", coap_socket_strerror()); switch (listen_addr->addr.sa.sa_family) { case AF_INET: break; case AF_INET6: /* Configure the socket as dual-stacked */ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, OPTVAL_T(&off), sizeof(off)) == COAP_SOCKET_ERROR) coap_log(LOG_ALERT, "coap_socket_bind_tcp: setsockopt IPV6_V6ONLY: %s\n", coap_socket_strerror()); break; default: coap_log(LOG_ALERT, "coap_socket_bind_tcp: unsupported sa_family\n"); } if (bind(sock->fd, &listen_addr->addr.sa, listen_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_ALERT, "coap_socket_bind_tcp: bind: %s\n", coap_socket_strerror()); goto error; } bound_addr->size = (socklen_t)sizeof(*bound_addr); if (getsockname(sock->fd, &bound_addr->addr.sa, &bound_addr->size) < 0) { coap_log(LOG_WARNING, "coap_socket_bind_tcp: getsockname: %s\n", coap_socket_strerror()); goto error; } if (listen(sock->fd, 5) == COAP_SOCKET_ERROR) { coap_log(LOG_ALERT, "coap_socket_bind_tcp: listen: %s\n", coap_socket_strerror()); goto error; } return 1; error: coap_socket_close(sock); return 0; } int coap_socket_accept_tcp(coap_socket_t *server, coap_socket_t *new_client, coap_address_t *local_addr, coap_address_t *remote_addr) { #ifdef _WIN32 u_long u_on = 1; #else int on = 1; #endif server->flags &= ~COAP_SOCKET_CAN_ACCEPT; new_client->fd = accept(server->fd, &remote_addr->addr.sa, &remote_addr->size); if (new_client->fd == COAP_INVALID_SOCKET) { coap_log(LOG_WARNING, "coap_socket_accept_tcp: accept: %s\n", coap_socket_strerror()); return 0; } if (getsockname( new_client->fd, &local_addr->addr.sa, &local_addr->size) < 0) coap_log(LOG_WARNING, "coap_socket_accept_tcp: getsockname: %s\n", coap_socket_strerror()); #ifdef _WIN32 if (ioctlsocket(new_client->fd, FIONBIO, &u_on) == COAP_SOCKET_ERROR) { #else if (ioctl(new_client->fd, FIONBIO, &on) == COAP_SOCKET_ERROR) { #endif coap_log(LOG_WARNING, "coap_socket_accept_tcp: ioctl FIONBIO: %s\n", coap_socket_strerror()); } return 1; } int coap_socket_connect_udp(coap_socket_t *sock, const coap_address_t *local_if, const coap_address_t *server, int default_port, coap_address_t *local_addr, coap_address_t *remote_addr) { int on = 1, off = 0; #ifdef _WIN32 u_long u_on = 1; #endif coap_address_t connect_addr; int is_mcast = coap_is_mcast(server); coap_address_copy(&connect_addr, server); sock->flags &= ~(COAP_SOCKET_CONNECTED | COAP_SOCKET_MULTICAST); sock->fd = socket(connect_addr.addr.sa.sa_family, SOCK_DGRAM, 0); if (sock->fd == COAP_INVALID_SOCKET) { coap_log(LOG_WARNING, "coap_socket_connect_udp: socket: %s\n", coap_socket_strerror()); goto error; } #ifdef _WIN32 if (ioctlsocket(sock->fd, FIONBIO, &u_on) == COAP_SOCKET_ERROR) { #else if (ioctl(sock->fd, FIONBIO, &on) == COAP_SOCKET_ERROR) { #endif coap_log(LOG_WARNING, "coap_socket_connect_udp: ioctl FIONBIO: %s\n", coap_socket_strerror()); } switch (connect_addr.addr.sa.sa_family) { case AF_INET: if (connect_addr.addr.sin.sin_port == 0) connect_addr.addr.sin.sin_port = htons(default_port); break; case AF_INET6: if (connect_addr.addr.sin6.sin6_port == 0) connect_addr.addr.sin6.sin6_port = htons(default_port); /* Configure the socket as dual-stacked */ if (setsockopt(sock->fd, IPPROTO_IPV6, IPV6_V6ONLY, OPTVAL_T(&off), sizeof(off)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_connect_udp: setsockopt IPV6_V6ONLY: %s\n", coap_socket_strerror()); break; default: coap_log(LOG_ALERT, "coap_socket_connect_udp: unsupported sa_family\n"); break; } if (local_if && local_if->addr.sa.sa_family) { if (setsockopt(sock->fd, SOL_SOCKET, SO_REUSEADDR, OPTVAL_T(&on), sizeof(on)) == COAP_SOCKET_ERROR) coap_log(LOG_WARNING, "coap_socket_connect_udp: setsockopt SO_REUSEADDR: %s\n", coap_socket_strerror()); if (bind(sock->fd, &local_if->addr.sa, local_if->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_udp: bind: %s\n", coap_socket_strerror()); goto error; } } /* special treatment for sockets that are used for multicast communication */ if (is_mcast) { if (getsockname(sock->fd, &local_addr->addr.sa, &local_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_udp: getsockname for multicast socket: %s\n", coap_socket_strerror()); } coap_address_copy(remote_addr, &connect_addr); sock->flags |= COAP_SOCKET_MULTICAST; return 1; } if (connect(sock->fd, &connect_addr.addr.sa, connect_addr.size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_udp: connect: %s\n", coap_socket_strerror()); goto error; } if (getsockname(sock->fd, &local_addr->addr.sa, &local_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_udp: getsockname: %s\n", coap_socket_strerror()); } if (getpeername(sock->fd, &remote_addr->addr.sa, &remote_addr->size) == COAP_SOCKET_ERROR) { coap_log(LOG_WARNING, "coap_socket_connect_udp: getpeername: %s\n", coap_socket_strerror()); } sock->flags |= COAP_SOCKET_CONNECTED; return 1; error: coap_socket_close(sock); return 0; } void coap_socket_close(coap_socket_t *sock) { if (sock->fd != COAP_INVALID_SOCKET) { coap_closesocket(sock->fd); sock->fd = COAP_INVALID_SOCKET; } sock->flags = COAP_SOCKET_EMPTY; } ssize_t coap_socket_write(coap_socket_t *sock, const uint8_t *data, size_t data_len) { ssize_t r; sock->flags &= ~(COAP_SOCKET_WANT_WRITE | COAP_SOCKET_CAN_WRITE); #ifdef _WIN32 r = send(sock->fd, (const char *)data, (int)data_len, 0); #else r = send(sock->fd, data, data_len, 0); #endif if (r == COAP_SOCKET_ERROR) { #ifdef _WIN32 if (WSAGetLastError() == WSAEWOULDBLOCK) { #elif EAGAIN != EWOULDBLOCK if (errno==EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { #else if (errno==EAGAIN || errno == EINTR) { #endif sock->flags |= COAP_SOCKET_WANT_WRITE; return 0; } coap_log(LOG_WARNING, "coap_socket_write: send: %s\n", coap_socket_strerror()); return -1; } if (r < (ssize_t)data_len) sock->flags |= COAP_SOCKET_WANT_WRITE; return r; } ssize_t coap_socket_read(coap_socket_t *sock, uint8_t *data, size_t data_len) { ssize_t r; #ifdef _WIN32 int error; #endif #ifdef _WIN32 r = recv(sock->fd, (char *)data, (int)data_len, 0); #else r = recv(sock->fd, data, data_len, 0); #endif if (r == 0) { /* graceful shutdown */ sock->flags &= ~COAP_SOCKET_CAN_READ; return -1; } else if (r == COAP_SOCKET_ERROR) { sock->flags &= ~COAP_SOCKET_CAN_READ; #ifdef _WIN32 error = WSAGetLastError(); if (error == WSAEWOULDBLOCK) { #elif EAGAIN != EWOULDBLOCK if (errno==EAGAIN || errno == EWOULDBLOCK || errno == EINTR) { #else if (errno==EAGAIN || errno == EINTR) { #endif return 0; } #ifdef _WIN32 if (error != WSAECONNRESET) #else if (errno != ECONNRESET) #endif coap_log(LOG_WARNING, "coap_socket_read: recv: %s\n", coap_socket_strerror()); return -1; } if (r < (ssize_t)data_len) sock->flags &= ~COAP_SOCKET_CAN_READ; return r; } #endif /* WITH_CONTIKI */ #if (!defined(WITH_CONTIKI)) != ( defined(HAVE_NETINET_IN_H) || defined(HAVE_WS2TCPIP_H) ) /* define struct in6_pktinfo and struct in_pktinfo if not available FIXME: check with configure */ struct in6_pktinfo { struct in6_addr ipi6_addr; /* src/dst IPv6 address */ unsigned int ipi6_ifindex; /* send/recv interface index */ }; #endif #if !defined(WITH_CONTIKI) && !defined(SOL_IP) /* Solaris expects level IPPROTO_IP for ancillary data. */ #define SOL_IP IPPROTO_IP #endif #ifdef __GNUC__ #define UNUSED_PARAM __attribute__ ((unused)) #else /* not a GCC */ #define UNUSED_PARAM #endif /* GCC */ #if defined(_WIN32) #include static __declspec(thread) LPFN_WSARECVMSG lpWSARecvMsg = NULL; /* Map struct WSABUF fields to their posix counterpart */ #define msghdr _WSAMSG #define msg_name name #define msg_namelen namelen #define msg_iov lpBuffers #define msg_iovlen dwBufferCount #define msg_control Control.buf #define msg_controllen Control.len #define iovec _WSABUF #define iov_base buf #define iov_len len #define iov_len_t u_long #undef CMSG_DATA #define CMSG_DATA WSA_CMSG_DATA #define ipi_spec_dst ipi_addr #else #define iov_len_t size_t #endif ssize_t coap_network_send(coap_socket_t *sock, const coap_session_t *session, const uint8_t *data, size_t datalen) { ssize_t bytes_written = 0; if (!coap_debug_send_packet()) { bytes_written = (ssize_t)datalen; #ifndef WITH_CONTIKI } else if (sock->flags & COAP_SOCKET_CONNECTED) { #ifdef _WIN32 bytes_written = send(sock->fd, (const char *)data, (int)datalen, 0); #else bytes_written = send(sock->fd, data, datalen, 0); #endif #endif } else { #ifndef WITH_CONTIKI #ifdef _WIN32 DWORD dwNumberOfBytesSent = 0; int r; #endif #ifndef COAP_BAD_RECVMSG /* a buffer large enough to hold all packet info types, ipv6 is the largest */ char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; struct msghdr mhdr; struct iovec iov[1]; const void *addr = &session->remote_addr.addr; assert(session); memcpy (&iov[0].iov_base, &data, sizeof (iov[0].iov_base)); iov[0].iov_len = (iov_len_t)datalen; memset(buf, 0, sizeof (buf)); memset(&mhdr, 0, sizeof(struct msghdr)); memcpy (&mhdr.msg_name, &addr, sizeof (mhdr.msg_name)); mhdr.msg_namelen = session->remote_addr.size; mhdr.msg_iov = iov; mhdr.msg_iovlen = 1; if (!coap_address_isany(&session->local_addr) && !coap_is_mcast(&session->local_addr)) switch (session->local_addr.addr.sa.sa_family) { case AF_INET6: { struct cmsghdr *cmsg; if (IN6_IS_ADDR_V4MAPPED(&session->local_addr.addr.sin6.sin6_addr)) { #if defined(IP_PKTINFO) struct in_pktinfo *pktinfo; mhdr.msg_control = buf; mhdr.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); cmsg = CMSG_FIRSTHDR(&mhdr); cmsg->cmsg_level = SOL_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); pktinfo->ipi_ifindex = session->ifindex; memcpy(&pktinfo->ipi_spec_dst, session->local_addr.addr.sin6.sin6_addr.s6_addr + 12, sizeof(pktinfo->ipi_spec_dst)); #elif defined(IP_SENDSRCADDR) mhdr.msg_control = buf; mhdr.msg_controllen = CMSG_SPACE(sizeof(struct in_addr)); cmsg = CMSG_FIRSTHDR(&mhdr); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_SENDSRCADDR; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); memcpy(CMSG_DATA(cmsg), session->local_addr.addr.sin6.sin6_addr.s6_addr + 12, sizeof(struct in_addr)); #endif /* IP_PKTINFO */ } else { struct in6_pktinfo *pktinfo; mhdr.msg_control = buf; mhdr.msg_controllen = CMSG_SPACE(sizeof(struct in6_pktinfo)); cmsg = CMSG_FIRSTHDR(&mhdr); cmsg->cmsg_level = IPPROTO_IPV6; cmsg->cmsg_type = IPV6_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg); pktinfo->ipi6_ifindex = session->ifindex; memcpy(&pktinfo->ipi6_addr, &session->local_addr.addr.sin6.sin6_addr, sizeof(pktinfo->ipi6_addr)); } break; } case AF_INET: { #if defined(IP_PKTINFO) struct cmsghdr *cmsg; struct in_pktinfo *pktinfo; mhdr.msg_control = buf; mhdr.msg_controllen = CMSG_SPACE(sizeof(struct in_pktinfo)); cmsg = CMSG_FIRSTHDR(&mhdr); cmsg->cmsg_level = SOL_IP; cmsg->cmsg_type = IP_PKTINFO; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); pktinfo = (struct in_pktinfo *)CMSG_DATA(cmsg); pktinfo->ipi_ifindex = session->ifindex; memcpy(&pktinfo->ipi_spec_dst, &session->local_addr.addr.sin.sin_addr, sizeof(pktinfo->ipi_spec_dst)); #elif defined(IP_SENDSRCADDR) struct cmsghdr *cmsg; mhdr.msg_control = buf; mhdr.msg_controllen = CMSG_SPACE(sizeof(struct in_addr)); cmsg = CMSG_FIRSTHDR(&mhdr); cmsg->cmsg_level = IPPROTO_IP; cmsg->cmsg_type = IP_SENDSRCADDR; cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); memcpy(CMSG_DATA(cmsg), &session->local_addr.addr.sin.sin_addr, sizeof(struct in_addr)); #endif /* IP_PKTINFO */ break; } default: /* error */ coap_log(LOG_WARNING, "protocol not supported\n"); bytes_written = -1; } #endif /* ! COAP_BAD_RECVMSG */ #ifdef _WIN32 r = WSASendMsg(sock->fd, &mhdr, 0 /*dwFlags*/, &dwNumberOfBytesSent, NULL /*lpOverlapped*/, NULL /*lpCompletionRoutine*/); if (r == 0) bytes_written = (ssize_t)dwNumberOfBytesSent; else bytes_written = -1; #else #ifndef COAP_BAD_RECVMSG bytes_written = sendmsg(sock->fd, &mhdr, 0); #else /* COAP_BAD_RECVMSG */ bytes_written = sendto(sock->fd, data, datalen, 0, &session->remote_addr.addr.sa, session->remote_addr.size); #endif /* COAP_BAD_RECVMSG */ #endif #else /* WITH_CONTIKI */ /* FIXME: untested */ /* FIXME: is there a way to check if send was successful? */ (void)datalen; (void)data; uip_udp_packet_sendto((struct uip_udp_conn *)sock->conn, data, datalen, &session->remote_addr.addr, session->remote_addr.port); bytes_written = datalen; #endif /* WITH_CONTIKI */ } if (bytes_written < 0) coap_log(LOG_CRIT, "coap_network_send: %s\n", coap_socket_strerror()); return bytes_written; } #define SIN6(A) ((struct sockaddr_in6 *)(A)) void coap_packet_get_memmapped(coap_packet_t *packet, unsigned char **address, size_t *length) { *address = packet->payload; *length = packet->length; } void coap_packet_set_addr(coap_packet_t *packet, const coap_address_t *src, const coap_address_t *dst) { coap_address_copy(&packet->src, src); coap_address_copy(&packet->dst, dst); } ssize_t coap_network_read(coap_socket_t *sock, coap_packet_t *packet) { ssize_t len = -1; assert(sock); assert(packet); if ((sock->flags & COAP_SOCKET_CAN_READ) == 0) { return -1; } else { /* clear has-data flag */ sock->flags &= ~COAP_SOCKET_CAN_READ; } #ifndef WITH_CONTIKI if (sock->flags & COAP_SOCKET_CONNECTED) { #ifdef _WIN32 len = recv(sock->fd, (char *)packet->payload, COAP_RXBUFFER_SIZE, 0); #else len = recv(sock->fd, packet->payload, COAP_RXBUFFER_SIZE, 0); #endif if (len < 0) { #ifdef _WIN32 if (WSAGetLastError() == WSAECONNRESET) { #else if (errno == ECONNREFUSED) { #endif /* client-side ICMP destination unreachable, ignore it */ coap_log(LOG_WARNING, "coap_network_read: unreachable\n"); return -2; } coap_log(LOG_WARNING, "coap_network_read: %s\n", coap_socket_strerror()); goto error; } else if (len > 0) { packet->length = (size_t)len; } } else { #endif /* WITH_CONTIKI */ #if defined(_WIN32) DWORD dwNumberOfBytesRecvd = 0; int r; #endif #if !defined(WITH_CONTIKI) #ifndef COAP_BAD_RECVMSG /* a buffer large enough to hold all packet info types, ipv6 is the largest */ char buf[CMSG_SPACE(sizeof(struct in6_pktinfo))]; struct msghdr mhdr; struct iovec iov[1]; iov[0].iov_base = packet->payload; iov[0].iov_len = (iov_len_t)COAP_RXBUFFER_SIZE; memset(&mhdr, 0, sizeof(struct msghdr)); mhdr.msg_name = (struct sockaddr*)&packet->src.addr; mhdr.msg_namelen = sizeof(packet->src.addr); mhdr.msg_iov = iov; mhdr.msg_iovlen = 1; mhdr.msg_control = buf; mhdr.msg_controllen = sizeof(buf); #if defined(_WIN32) if (!lpWSARecvMsg) { GUID wsaid = WSAID_WSARECVMSG; DWORD cbBytesReturned = 0; if (WSAIoctl(sock->fd, SIO_GET_EXTENSION_FUNCTION_POINTER, &wsaid, sizeof(wsaid), &lpWSARecvMsg, sizeof(lpWSARecvMsg), &cbBytesReturned, NULL, NULL) != 0) { coap_log(LOG_WARNING, "coap_network_read: no WSARecvMsg\n"); return -1; } } r = lpWSARecvMsg(sock->fd, &mhdr, &dwNumberOfBytesRecvd, NULL /* LPWSAOVERLAPPED */, NULL /* LPWSAOVERLAPPED_COMPLETION_ROUTINE */); if (r == 0) len = (ssize_t)dwNumberOfBytesRecvd; #else len = recvmsg(sock->fd, &mhdr, 0); #endif #else /* COAP_BAD_RECVMSG */ packet->src.size = packet->src.size; len = recvfrom(sock->fd, packet->payload, COAP_RXBUFFER_SIZE, 0, &packet->src.addr.sa, &packet->src.size); #endif /* COAP_BAD_RECVMSG */ if (len < 0) { #ifdef _WIN32 if (WSAGetLastError() == WSAECONNRESET) { #else if (errno == ECONNREFUSED) { #endif /* server-side ICMP destination unreachable, ignore it. The destination address is in msg_name. */ return 0; } coap_log(LOG_WARNING, "coap_network_read: %s\n", coap_socket_strerror()); goto error; } else { #ifndef COAP_BAD_RECVMSG struct cmsghdr *cmsg; packet->src.size = mhdr.msg_namelen; packet->length = (size_t)len; /* Walk through ancillary data records until the local interface * is found where the data was received. */ for (cmsg = CMSG_FIRSTHDR(&mhdr); cmsg; cmsg = CMSG_NXTHDR(&mhdr, cmsg)) { /* get the local interface for IPv6 */ if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == IPV6_PKTINFO) { union { uint8_t *c; struct in6_pktinfo *p; } u; u.c = CMSG_DATA(cmsg); packet->ifindex = (int)(u.p->ipi6_ifindex); memcpy(&packet->dst.addr.sin6.sin6_addr, &u.p->ipi6_addr, sizeof(struct in6_addr)); break; } /* local interface for IPv4 */ #if defined(IP_PKTINFO) if (cmsg->cmsg_level == SOL_IP && cmsg->cmsg_type == IP_PKTINFO) { union { uint8_t *c; struct in_pktinfo *p; } u; u.c = CMSG_DATA(cmsg); packet->ifindex = u.p->ipi_ifindex; if (packet->dst.addr.sa.sa_family == AF_INET6) { memset(packet->dst.addr.sin6.sin6_addr.s6_addr, 0, 10); packet->dst.addr.sin6.sin6_addr.s6_addr[10] = 0xff; packet->dst.addr.sin6.sin6_addr.s6_addr[11] = 0xff; memcpy(packet->dst.addr.sin6.sin6_addr.s6_addr + 12, &u.p->ipi_addr, sizeof(struct in_addr)); } else { memcpy(&packet->dst.addr.sin.sin_addr, &u.p->ipi_addr, sizeof(struct in_addr)); } break; } #elif defined(IP_RECVDSTADDR) if (cmsg->cmsg_level == IPPROTO_IP && cmsg->cmsg_type == IP_RECVDSTADDR) { packet->ifindex = 0; memcpy(&packet->dst.addr.sin.sin_addr, CMSG_DATA(cmsg), sizeof(struct in_addr)); break; } #endif /* IP_PKTINFO */ } #else /* COAP_BAD_RECVMSG */ packet->length = (size_t)len; packet->ifindex = 0; if (getsockname(sock->fd, &packet->dst.addr.sa, &packet->dst.size) < 0) { coap_log(LOG_DEBUG, "Cannot determine local port\n"); goto error; } #endif /* COAP_BAD_RECVMSG */ } #endif /* !defined(WITH_CONTIKI) */ #ifdef WITH_CONTIKI /* FIXME: untested, make this work */ #define UIP_IP_BUF ((struct uip_ip_hdr *)&uip_buf[UIP_LLH_LEN]) #define UIP_UDP_BUF ((struct uip_udp_hdr *)&uip_buf[UIP_LLIPH_LEN]) if (uip_newdata()) { uip_ipaddr_copy(&packet->src.addr, &UIP_IP_BUF->srcipaddr); packet->src.port = UIP_UDP_BUF->srcport; uip_ipaddr_copy(&(packet)->dst.addr, &UIP_IP_BUF->destipaddr); packet->dst.port = UIP_UDP_BUF->destport; len = uip_datalen(); if (len > COAP_RXBUFFER_SIZE) { /* FIXME: we might want to send back a response */ coap_log(LOG_WARNING, "discarded oversized packet\n"); return -1; } ((char *)uip_appdata)[len] = 0; #ifndef NDEBUG if (LOG_DEBUG <= coap_get_log_level()) { #ifndef INET6_ADDRSTRLEN #define INET6_ADDRSTRLEN 40 #endif unsigned char addr_str[INET6_ADDRSTRLEN + 8]; if (coap_print_addr(&packet->src, addr_str, INET6_ADDRSTRLEN + 8)) { coap_log(LOG_DEBUG, "received %zd bytes from %s\n", len, addr_str); } } #endif /* NDEBUG */ packet->length = len; memcpy(&packet->payload, uip_appdata, len); } #undef UIP_IP_BUF #undef UIP_UDP_BUF #endif /* WITH_CONTIKI */ #ifndef WITH_CONTIKI } #endif /* WITH_CONTIKI */ if (len >= 0) return len; #if !defined(WITH_CONTIKI) error: #endif return -1; } #if !defined(WITH_CONTIKI) unsigned int coap_write(coap_context_t *ctx, coap_socket_t *sockets[], unsigned int max_sockets, unsigned int *num_sockets, coap_tick_t now) { coap_queue_t *nextpdu; coap_endpoint_t *ep; coap_session_t *s; coap_tick_t session_timeout; coap_tick_t timeout = 0; coap_session_t *tmp; *num_sockets = 0; /* Check to see if we need to send off any Observe requests */ coap_check_notify(ctx); if (ctx->session_timeout > 0) session_timeout = ctx->session_timeout * COAP_TICKS_PER_SECOND; else session_timeout = COAP_DEFAULT_SESSION_TIMEOUT * COAP_TICKS_PER_SECOND; LL_FOREACH(ctx->endpoint, ep) { if (ep->sock.flags & (COAP_SOCKET_WANT_READ | COAP_SOCKET_WANT_WRITE | COAP_SOCKET_WANT_ACCEPT)) { if (*num_sockets < max_sockets) sockets[(*num_sockets)++] = &ep->sock; } LL_FOREACH_SAFE(ep->sessions, s, tmp) { if (s->type == COAP_SESSION_TYPE_SERVER && s->ref == 0 && s->delayqueue == NULL && (s->last_rx_tx + session_timeout <= now || s->state == COAP_SESSION_STATE_NONE)) { coap_session_free(s); } else { if (s->type == COAP_SESSION_TYPE_SERVER && s->ref == 0 && s->delayqueue == NULL) { coap_tick_t s_timeout = (s->last_rx_tx + session_timeout) - now; if (timeout == 0 || s_timeout < timeout) timeout = s_timeout; } if (s->sock.flags & (COAP_SOCKET_WANT_READ | COAP_SOCKET_WANT_WRITE)) { if (*num_sockets < max_sockets) sockets[(*num_sockets)++] = &s->sock; } } } } LL_FOREACH_SAFE(ctx->sessions, s, tmp) { if ( s->type == COAP_SESSION_TYPE_CLIENT && COAP_PROTO_RELIABLE(s->proto) && s->state == COAP_SESSION_STATE_ESTABLISHED && ctx->ping_timeout > 0 ) { coap_tick_t s_timeout; if (s->last_rx_tx + ctx->ping_timeout * COAP_TICKS_PER_SECOND <= now) { if ((s->last_ping > 0 && s->last_pong < s->last_ping) || coap_session_send_ping(s) == COAP_INVALID_TID) { /* Make sure the session object is not deleted in the callback */ coap_session_reference(s); coap_session_disconnected(s, COAP_NACK_NOT_DELIVERABLE); coap_session_release(s); continue; } s->last_rx_tx = now; s->last_ping = now; } s_timeout = (s->last_rx_tx + ctx->ping_timeout * COAP_TICKS_PER_SECOND) - now; if (timeout == 0 || s_timeout < timeout) timeout = s_timeout; } if ( s->type == COAP_SESSION_TYPE_CLIENT && COAP_PROTO_RELIABLE(s->proto) && s->state == COAP_SESSION_STATE_CSM && ctx->csm_timeout > 0 ) { coap_tick_t s_timeout; if (s->csm_tx == 0) { s->csm_tx = now; } else if (s->csm_tx + ctx->csm_timeout * COAP_TICKS_PER_SECOND <= now) { /* Make sure the session object is not deleted in the callback */ coap_session_reference(s); coap_session_disconnected(s, COAP_NACK_NOT_DELIVERABLE); coap_session_release(s); continue; } s_timeout = (s->csm_tx + ctx->csm_timeout * COAP_TICKS_PER_SECOND) - now; if (timeout == 0 || s_timeout < timeout) timeout = s_timeout; } if (s->sock.flags & (COAP_SOCKET_WANT_READ | COAP_SOCKET_WANT_WRITE | COAP_SOCKET_WANT_CONNECT)) { if (*num_sockets < max_sockets) sockets[(*num_sockets)++] = &s->sock; } } nextpdu = coap_peek_next(ctx); while (nextpdu && now >= ctx->sendqueue_basetime && nextpdu->t <= now - ctx->sendqueue_basetime) { coap_retransmit(ctx, coap_pop_next(ctx)); nextpdu = coap_peek_next(ctx); } if (nextpdu && (timeout == 0 || nextpdu->t - ( now - ctx->sendqueue_basetime ) < timeout)) timeout = nextpdu->t - (now - ctx->sendqueue_basetime); if (ctx->dtls_context) { if (coap_dtls_is_context_timeout()) { coap_tick_t tls_timeout = coap_dtls_get_context_timeout(ctx->dtls_context); if (tls_timeout > 0) { if (tls_timeout < now + COAP_TICKS_PER_SECOND / 10) tls_timeout = now + COAP_TICKS_PER_SECOND / 10; coap_log(LOG_DEBUG, "** DTLS global timeout set to %dms\n", (int)((tls_timeout - now) * 1000 / COAP_TICKS_PER_SECOND)); if (timeout == 0 || tls_timeout - now < timeout) timeout = tls_timeout - now; } } else { LL_FOREACH(ctx->endpoint, ep) { if (ep->proto == COAP_PROTO_DTLS) { LL_FOREACH(ep->sessions, s) { if (s->proto == COAP_PROTO_DTLS && s->tls) { coap_tick_t tls_timeout = coap_dtls_get_timeout(s); while (tls_timeout > 0 && tls_timeout <= now) { coap_log(LOG_DEBUG, "** %s: DTLS retransmit timeout\n", coap_session_str(s)); coap_dtls_handle_timeout(s); if (s->tls) tls_timeout = coap_dtls_get_timeout(s); else { tls_timeout = 0; timeout = 1; } } if (tls_timeout > 0 && (timeout == 0 || tls_timeout - now < timeout)) timeout = tls_timeout - now; } } } } LL_FOREACH(ctx->sessions, s) { if (s->proto == COAP_PROTO_DTLS && s->tls) { coap_tick_t tls_timeout = coap_dtls_get_timeout(s); while (tls_timeout > 0 && tls_timeout <= now) { coap_log(LOG_DEBUG, "** %s: DTLS retransmit timeout\n", coap_session_str(s)); coap_dtls_handle_timeout(s); if (s->tls) tls_timeout = coap_dtls_get_timeout(s); else { tls_timeout = 0; timeout = 1; } } if (tls_timeout > 0 && (timeout == 0 || tls_timeout - now < timeout)) timeout = tls_timeout - now; } } } } return (unsigned int)((timeout * 1000 + COAP_TICKS_PER_SECOND - 1) / COAP_TICKS_PER_SECOND); } int coap_run_once(coap_context_t *ctx, unsigned timeout_ms) { fd_set readfds, writefds, exceptfds; coap_fd_t nfds = 0; struct timeval tv; coap_tick_t before, now; int result; coap_socket_t *sockets[64]; unsigned int num_sockets = 0, i, timeout; coap_ticks(&before); timeout = coap_write(ctx, sockets, (unsigned int)(sizeof(sockets) / sizeof(sockets[0])), &num_sockets, before); if (timeout == 0 || timeout_ms < timeout) timeout = timeout_ms; FD_ZERO(&readfds); FD_ZERO(&writefds); FD_ZERO(&exceptfds); for (i = 0; i < num_sockets; i++) { if (sockets[i]->fd + 1 > nfds) nfds = sockets[i]->fd + 1; if (sockets[i]->flags & COAP_SOCKET_WANT_READ) FD_SET(sockets[i]->fd, &readfds); if (sockets[i]->flags & COAP_SOCKET_WANT_WRITE) FD_SET(sockets[i]->fd, &writefds); if (sockets[i]->flags & COAP_SOCKET_WANT_ACCEPT) FD_SET(sockets[i]->fd, &readfds); if (sockets[i]->flags & COAP_SOCKET_WANT_CONNECT) { FD_SET(sockets[i]->fd, &writefds); FD_SET(sockets[i]->fd, &exceptfds); } } if ( timeout > 0 ) { tv.tv_usec = (timeout % 1000) * 1000; tv.tv_sec = (long)(timeout / 1000); } result = select(nfds, &readfds, &writefds, &exceptfds, timeout > 0 ? &tv : NULL); if (result < 0) { /* error */ #ifdef _WIN32 if (WSAGetLastError() != WSAEINVAL) { /* May happen because of ICMP */ #else if (errno != EINTR) { #endif coap_log(LOG_DEBUG, "%s", coap_socket_strerror()); return -1; } } if (result > 0) { for (i = 0; i < num_sockets; i++) { if ((sockets[i]->flags & COAP_SOCKET_WANT_READ) && FD_ISSET(sockets[i]->fd, &readfds)) sockets[i]->flags |= COAP_SOCKET_CAN_READ; if ((sockets[i]->flags & COAP_SOCKET_WANT_ACCEPT) && FD_ISSET(sockets[i]->fd, &readfds)) sockets[i]->flags |= COAP_SOCKET_CAN_ACCEPT; if ((sockets[i]->flags & COAP_SOCKET_WANT_WRITE) && FD_ISSET(sockets[i]->fd, &writefds)) sockets[i]->flags |= COAP_SOCKET_CAN_WRITE; if ((sockets[i]->flags & COAP_SOCKET_WANT_CONNECT) && (FD_ISSET(sockets[i]->fd, &writefds) || FD_ISSET(sockets[i]->fd, &exceptfds))) sockets[i]->flags |= COAP_SOCKET_CAN_CONNECT; } } coap_ticks(&now); coap_read(ctx, now); return (int)(((now - before) * 1000) / COAP_TICKS_PER_SECOND); } #else int coap_run_once(coap_context_t *ctx, unsigned int timeout_ms) { return -1; } unsigned int coap_write(coap_context_t *ctx, coap_socket_t *sockets[], unsigned int max_sockets, unsigned int *num_sockets, coap_tick_t now) { *num_sockets = 0; return 0; } #endif #ifdef _WIN32 static const char *coap_socket_format_errno(int error) { static char szError[256]; if (FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, (DWORD)error, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)szError, (DWORD)sizeof(szError), NULL) == 0) strcpy(szError, "Unknown error"); return szError; } const char *coap_socket_strerror(void) { return coap_socket_format_errno(WSAGetLastError()); } #else #ifndef WITH_CONTIKI static const char *coap_socket_format_errno(int error) { return strerror(error); } #endif /* WITH_CONTIKI */ const char *coap_socket_strerror(void) { return strerror(errno); } #endif ssize_t coap_socket_send(coap_socket_t *sock, coap_session_t *session, const uint8_t *data, size_t data_len) { return session->context->network_send(sock, session, data, data_len); } #undef SIN6