/* * Copyright (c) 2016-2017, Linaro Limited * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "handle.h" #include "__tee_isocket_defines.h" #include "__tee_ipsocket.h" #include "__tee_tcpsocket_defines.h" #include "__tee_tcpsocket_defines_extensions.h" #include "__tee_udpsocket_defines.h" #ifndef __aligned #define __aligned(x) __attribute__((__aligned__(x))) #endif #include /* * Used when checking how much data we have queued. * * For SOCK_DGRAM we try to be accurate up to 4096 bytes as * that's our arbitrary chosen sensible upper size (with * some margin). Larger size doesn't make much sense since * anything larger than the MTU is bound to cause trouble * on a congested network. * * For SOCK_STREAM we chose the same upper limit for * simplicity. It doesn't matter if there's more queued, * no data will be lost. */ #define SUPP_MAX_PEEK_LEN 4096 struct sock_instance { uint32_t id; struct handle_db db; TAILQ_ENTRY(sock_instance) link; }; static pthread_mutex_t sock_mutex = PTHREAD_MUTEX_INITIALIZER; TAILQ_HEAD(, sock_instance) sock_instances = TAILQ_HEAD_INITIALIZER(sock_instances); static void sock_lock(void) { pthread_mutex_lock(&sock_mutex); } static void sock_unlock(void) { pthread_mutex_unlock(&sock_mutex); } static struct sock_instance *sock_instance_find(uint32_t instance_id) { struct sock_instance *si = NULL; TAILQ_FOREACH(si, &sock_instances, link) { if (si->id == instance_id) return si; } return NULL; } static void *fd_to_handle_ptr(int fd) { uintptr_t ptr = 0; assert(fd >= 0); ptr = fd + 1; return (void *)ptr; } static int handle_ptr_to_fd(void *ptr) { assert(ptr); return (uintptr_t)ptr - 1; } static int sock_handle_get(uint32_t instance_id, int fd) { int handle = -1; struct sock_instance *si = NULL; sock_lock(); si = sock_instance_find(instance_id); if (!si) { si = calloc(1, sizeof(*si)); if (!si) goto out; si->id = instance_id; TAILQ_INSERT_TAIL(&sock_instances, si, link); } handle = handle_get(&si->db, fd_to_handle_ptr(fd)); out: sock_unlock(); return handle; } static int sock_handle_to_fd(uint32_t instance_id, uint32_t handle) { int fd = -1; struct sock_instance *si = NULL; sock_lock(); si = sock_instance_find(instance_id); if (si) fd = handle_ptr_to_fd(handle_lookup(&si->db, handle)); sock_unlock(); return fd; } static void sock_handle_put(uint32_t instance_id, uint32_t handle) { struct sock_instance *si = NULL; sock_lock(); si = sock_instance_find(instance_id); if (si) handle_put(&si->db, handle); sock_unlock(); } static bool chk_pt(struct tee_ioctl_param *param, uint32_t type) { return (param->attr & TEE_IOCTL_PARAM_ATTR_TYPE_MASK) == type; } static int fd_flags_add(int fd, int flags) { int val = 0; val = fcntl(fd, F_GETFD, 0); if (val == -1) return -1; val |= flags; return fcntl(fd, F_SETFL, val); } static TEEC_Result sock_connect(uint32_t ip_vers, unsigned int protocol, const char *server, uint16_t port, int *ret_fd) { TEEC_Result r = TEEC_ERROR_GENERIC; struct addrinfo *res0 = NULL; struct addrinfo *res = NULL; int fd = -1; char port_name[10] = { 0 }; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); snprintf(port_name, sizeof(port_name), "%" PRIu16, port); switch (ip_vers) { case TEE_IP_VERSION_DC: hints.ai_family = AF_UNSPEC; break; case TEE_IP_VERSION_4: hints.ai_family = AF_INET; break; case TEE_IP_VERSION_6: hints.ai_family = AF_INET6; break; default: return TEEC_ERROR_BAD_PARAMETERS; } if (protocol == TEE_ISOCKET_PROTOCOLID_TCP) hints.ai_socktype = SOCK_STREAM; else if (protocol == TEE_ISOCKET_PROTOCOLID_UDP) hints.ai_socktype = SOCK_DGRAM; else return TEEC_ERROR_BAD_PARAMETERS; if (getaddrinfo(server, port_name, &hints, &res0)) return TEE_ISOCKET_ERROR_HOSTNAME; for (res = res0; res; res = res->ai_next) { fd = socket(res->ai_family, res->ai_socktype, res->ai_protocol); if (fd == -1) { if (errno == ENOMEM || errno == ENOBUFS) r = TEE_ISOCKET_ERROR_OUT_OF_RESOURCES; else r = TEEC_ERROR_GENERIC; continue; } if (connect(fd, res->ai_addr, res->ai_addrlen)) { if (errno == ETIMEDOUT) r = TEE_ISOCKET_ERROR_TIMEOUT; else r = TEEC_ERROR_COMMUNICATION; close(fd); fd = -1; continue; } if (fd_flags_add(fd, O_NONBLOCK)) { close(fd); fd = -1; r = TEEC_ERROR_GENERIC; break; } r = TEEC_SUCCESS; break; } freeaddrinfo(res0); *ret_fd = fd; return r; } static TEEC_Result tee_socket_open(size_t num_params, struct tee_ioctl_param *params) { TEEC_Result res = TEEC_ERROR_GENERIC; int handle = 0; int fd = 0; uint32_t instance_id = 0; char *server = NULL; uint32_t ip_vers = 0; uint16_t port = 0; uint32_t protocol = 0; if (num_params != 4 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) || !chk_pt(params + 3, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_OUTPUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; port = params[1].a; protocol = params[1].b; ip_vers = params[1].c; server = tee_supp_param_to_va(params + 2); if (!server || server[MEMREF_SIZE(params + 2) - 1] != '\0') return TEE_ISOCKET_ERROR_HOSTNAME; res = sock_connect(ip_vers, protocol, server, port, &fd); if (res != TEEC_SUCCESS) return res; handle = sock_handle_get(instance_id, fd); if (handle < 0) { close(fd); return TEEC_ERROR_OUT_OF_MEMORY; } params[3].a = handle; return TEEC_SUCCESS; } static TEEC_Result tee_socket_close(size_t num_params, struct tee_ioctl_param *params) { int handle = 0; uint32_t instance_id = 0; int fd = 0; if (num_params != 1 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; handle = params[0].c; fd = sock_handle_to_fd(instance_id, handle); if (fd < 0) return TEEC_ERROR_BAD_PARAMETERS; sock_handle_put(instance_id, handle); if (close(fd)) { EMSG("tee_socket_close: close(%d): %s", fd, strerror(errno)); return TEEC_ERROR_GENERIC; } return TEEC_SUCCESS; } static void sock_close_cb(int handle, void *ptr, void *arg) { struct sock_instance *si = arg; int fd = handle_ptr_to_fd(ptr); if (close(fd)) EMSG("sock_close_cb instance_id %d handle %d fd %d: %s", si->id, handle, fd, strerror(errno)); } static TEEC_Result tee_socket_close_all(size_t num_params, struct tee_ioctl_param *params) { uint32_t instance_id = 0; struct sock_instance *si = NULL; if (num_params != 1 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; sock_lock(); si = sock_instance_find(instance_id); if (si) handle_foreach_put(&si->db, sock_close_cb, si); sock_unlock(); return TEEC_SUCCESS; } #define TS_NSEC_PER_SEC 1000000000 static void ts_add(const struct timespec *a, const struct timespec *b, struct timespec *res) { res->tv_sec = a->tv_sec + b->tv_sec; res->tv_nsec = a->tv_nsec + b->tv_nsec; if (res->tv_nsec >= TS_NSEC_PER_SEC) { res->tv_sec++; res->tv_nsec -= TS_NSEC_PER_SEC; } } static int ts_diff_to_polltimeout(const struct timespec *a, const struct timespec *b) { struct timespec diff; memset(&diff, 0, sizeof(diff)); diff.tv_sec = a->tv_sec - b->tv_sec; diff.tv_nsec = a->tv_nsec - b->tv_nsec; if (a->tv_nsec < b->tv_nsec) { diff.tv_nsec += TS_NSEC_PER_SEC; diff.tv_sec--; } if ((diff.tv_sec - 1) > (INT_MAX / 1000)) return INT_MAX; return diff.tv_sec * 1000 + diff.tv_nsec / (TS_NSEC_PER_SEC / 1000); } static void ts_delay_from_millis(uint32_t millis, struct timespec *res) { res->tv_sec = millis / 1000; res->tv_nsec = (millis % 1000) * (TS_NSEC_PER_SEC / 1000); } static TEEC_Result poll_with_timeout(struct pollfd *pfd, nfds_t nfds, uint32_t timeout) { int to = 0; int r = 0; struct timespec now; struct timespec until; memset(&now, 0, sizeof(now)); memset(&until, 0, sizeof(until)); if (timeout == OPTEE_MRC_SOCKET_TIMEOUT_BLOCKING) { to = -1; } else { struct timespec delay; memset(&delay, 0, sizeof(delay)); ts_delay_from_millis(timeout, &delay); if (clock_gettime(CLOCK_REALTIME, &now)) return TEEC_ERROR_GENERIC; ts_add(&now, &delay, &until); } while (true) { if (to != -1) to = ts_diff_to_polltimeout(&until, &now); r = poll(pfd, nfds, to); if (!r) return TEE_ISOCKET_ERROR_TIMEOUT; if (r == -1) { /* * If we're interrupted by a signal treat * recalculate the timeout (if needed) and wait * again. */ if (errno == EINTR) { if (to != -1 && clock_gettime(CLOCK_REALTIME, &now)) return TEEC_ERROR_GENERIC; continue; } return TEEC_ERROR_BAD_PARAMETERS; } return TEEC_SUCCESS; } } static TEEC_Result write_with_timeout(int fd, const void *buf, size_t *blen, uint32_t timeout) { TEEC_Result res = TEEC_ERROR_GENERIC; struct pollfd pfd = { .fd = fd, .events = POLLOUT }; ssize_t r = 0; res = poll_with_timeout(&pfd, 1, timeout); if (res != TEEC_SUCCESS) return res; r = write(fd, buf, *blen); if (r == -1) { if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR) return TEE_ISOCKET_ERROR_TIMEOUT; return TEEC_ERROR_BAD_PARAMETERS; } *blen = r; return TEEC_SUCCESS; } static TEEC_Result tee_socket_send(size_t num_params, struct tee_ioctl_param *params) { TEEC_Result res = TEEC_ERROR_GENERIC; int handle = 0; int fd = 0; uint32_t instance_id = 0; void *buf = NULL; size_t bytes = 0; if (num_params != 3 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT) || !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INOUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; handle = params[0].c; fd = sock_handle_to_fd(instance_id, handle); if (fd < 0) return TEEC_ERROR_BAD_PARAMETERS; buf = tee_supp_param_to_va(params + 1); bytes = MEMREF_SIZE(params + 1); res = write_with_timeout(fd, buf, &bytes, params[2].a); if (res == TEEC_SUCCESS) params[2].b = bytes; return res; } static ssize_t recv_with_out_flags(int fd, void *buf, size_t len, int inflags, int *out_flags) { ssize_t r = 0; while (true) { struct iovec iov = { .iov_base = buf, .iov_len = len, }; struct msghdr msg = { .msg_iov = &iov, .msg_iovlen = 1, }; r = recvmsg(fd, &msg, inflags); if (r < 0) { /* * If the syscall was just interrupted by a signal * delivery, try again. */ if (errno == EINTR) continue; return r; } *out_flags = msg.msg_flags; return r; } } static TEEC_Result read_with_timeout(int fd, void *buf, size_t *blen, uint32_t timeout) { TEEC_Result res = TEEC_ERROR_GENERIC; struct pollfd pfd = { .fd = fd, .events = POLLIN }; int socktype = 0; socklen_t l = sizeof(socktype); size_t peek_len = 0; int out_flags = 0; ssize_t r = 0; int e = 0; if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &l)) return TEEC_ERROR_BAD_PARAMETERS; if (*blen) { /* If *blen == 0, the timeout parameter has no effect. */ res = poll_with_timeout(&pfd, 1, timeout); if (res != TEEC_SUCCESS) return res; } if ((socktype == SOCK_DGRAM && *blen < SUPP_MAX_PEEK_LEN) || !*blen) { /* Check how much data we have queued. */ void *b = malloc(SUPP_MAX_PEEK_LEN); if (!b) return TEEC_ERROR_OUT_OF_MEMORY; r = recv_with_out_flags(fd, b, SUPP_MAX_PEEK_LEN, MSG_PEEK | MSG_DONTWAIT, &out_flags); e = errno; free(b); if (r < 0) goto err; /* * If the message was truncated we know that it's at least * one byte larger. */ if (out_flags & MSG_TRUNC) r++; if (!*blen) { *blen = r; return TEEC_SUCCESS; } peek_len = r; } r = recv_with_out_flags(fd, buf, *blen, MSG_DONTWAIT, &out_flags); if (r == -1) { e = errno; goto err; } if (socktype == SOCK_DGRAM && (out_flags & MSG_TRUNC)) { /* * The datagram has been truncated, return the best length * we have to indicate that. */ if (peek_len > (size_t)r) *blen = peek_len; else *blen = r + 1; } else { *blen = r; } return TEEC_SUCCESS; err: if (e == EAGAIN || e == EWOULDBLOCK) { /* * If *blen is supplied as 0 then we're not supposed wait * for data so the call to poll has been skipped. In case * there is no data available recvmsg() will return an * error with errno set to EAGAIN or EWOULDBLOCK. */ if (!*blen) return TEEC_SUCCESS; return TEE_ISOCKET_ERROR_TIMEOUT; } return TEEC_ERROR_BAD_PARAMETERS; } static TEEC_Result tee_socket_recv(size_t num_params, struct tee_ioctl_param *params) { TEEC_Result res = TEEC_ERROR_GENERIC; int handle = 0; int fd = 0; uint32_t instance_id = 0; void *buf = NULL; size_t bytes = 0; if (num_params != 3 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_OUTPUT) || !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; handle = params[0].c; fd = sock_handle_to_fd(instance_id, handle); if (fd < 0) return TEEC_ERROR_BAD_PARAMETERS; buf = tee_supp_param_to_va(params + 1); bytes = MEMREF_SIZE(params + 1); res = read_with_timeout(fd, buf, &bytes, params[2].a); if (res == TEEC_SUCCESS) MEMREF_SIZE(params + 1) = bytes; return res; } static TEEC_Result tee_socket_ioctl_tcp(int fd, uint32_t command, void *buf, size_t *blen) { switch (command) { case TEE_TCP_SET_RECVBUF: if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, buf, *blen)) return TEEC_ERROR_BAD_PARAMETERS; return TEEC_SUCCESS; case TEE_TCP_SET_SENDBUF: if (setsockopt(fd, SOL_SOCKET, SO_SNDBUF, buf, *blen)) return TEEC_ERROR_BAD_PARAMETERS; return TEEC_SUCCESS; default: return TEEC_ERROR_NOT_SUPPORTED; } } static TEEC_Result sa_set_port(struct sockaddr *sa, socklen_t slen, uint16_t port) { if (sa->sa_family == AF_INET) { struct sockaddr_in *sain = (void *)sa; if (slen < (socklen_t)sizeof(*sain)) return TEEC_ERROR_BAD_PARAMETERS; sain->sin_port = htons(port); return TEEC_SUCCESS; } if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sain6 = (void *)sa; if (slen < (socklen_t)sizeof(*sain6)) return TEEC_ERROR_BAD_PARAMETERS; sain6->sin6_port = htons(port); return TEEC_SUCCESS; } return TEEC_ERROR_BAD_PARAMETERS; } static TEEC_Result sa_get_port(struct sockaddr *sa, socklen_t slen, uint16_t *port) { if (sa->sa_family == AF_INET) { struct sockaddr_in *sain = (void *)sa; if (slen < (socklen_t)sizeof(*sain)) return TEEC_ERROR_BAD_PARAMETERS; *port = ntohs(sain->sin_port); return TEEC_SUCCESS; } if (sa->sa_family == AF_INET6) { struct sockaddr_in6 *sain6 = (void *)sa; if (slen < (socklen_t)sizeof(*sain6)) return TEEC_ERROR_BAD_PARAMETERS; *port = ntohs(sain6->sin6_port); return TEEC_SUCCESS; } return TEEC_ERROR_BAD_PARAMETERS; } static TEEC_Result udp_changeaddr(int fd, int family, const char *server, uint16_t port) { TEEC_Result r = TEE_ISOCKET_ERROR_HOSTNAME; struct addrinfo *res0 = NULL; struct addrinfo *res = NULL; char port_name[10] = { 0 }; struct addrinfo hints; memset(&hints, 0, sizeof(hints)); snprintf(port_name, sizeof(port_name), "%" PRIu16, port); hints.ai_family = family; hints.ai_socktype = SOCK_DGRAM; if (getaddrinfo(server, port_name, &hints, &res0)) return TEE_ISOCKET_ERROR_HOSTNAME; for (res = res0; res; res = res->ai_next) { if (connect(fd, res->ai_addr, res->ai_addrlen)) { if (errno == ETIMEDOUT) r = TEE_ISOCKET_ERROR_TIMEOUT; else r = TEEC_ERROR_COMMUNICATION; continue; } r = TEEC_SUCCESS; break; } freeaddrinfo(res0); return r; } static TEEC_Result tee_socket_ioctl_udp(int fd, uint32_t command, void *buf, size_t *blen) { TEEC_Result res = TEEC_ERROR_GENERIC; uint16_t port = 0; struct sockaddr_storage sass; struct sockaddr *sa = (struct sockaddr *)&sass; socklen_t len = sizeof(sass); memset(&sass, 0, sizeof(sass)); if (getpeername(fd, sa, &len)) return TEEC_ERROR_BAD_PARAMETERS; switch (command) { case TEE_UDP_CHANGEADDR: res = sa_get_port(sa, len, &port); if (res != TEEC_SUCCESS) return res; if (!blen || *((char *)buf + *blen - 1) != '\0') return TEE_ISOCKET_ERROR_HOSTNAME; return udp_changeaddr(fd, sa->sa_family, buf, port); case TEE_UDP_CHANGEPORT: if (*blen != sizeof(port)) return TEEC_ERROR_BAD_PARAMETERS; memcpy(&port, buf, sizeof(port)); res = sa_set_port(sa, len, port); if (res != TEEC_SUCCESS) return res; if (connect(fd, sa, len)) return TEEC_ERROR_GENERIC; return TEEC_SUCCESS; default: return TEEC_ERROR_NOT_SUPPORTED; } } static TEEC_Result tee_socket_ioctl(size_t num_params, struct tee_ioctl_param *params) { TEEC_Result res = TEEC_ERROR_GENERIC; int handle = 0; int fd = 0; uint32_t instance_id = 0; uint32_t command = 0; void *buf = NULL; int socktype = 0; socklen_t l = 0; size_t sz = 0; if (num_params != 3 || !chk_pt(params + 0, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT) || !chk_pt(params + 1, TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT) || !chk_pt(params + 2, TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT)) return TEEC_ERROR_BAD_PARAMETERS; instance_id = params[0].b; handle = params[0].c; command = params[2].a; fd = sock_handle_to_fd(instance_id, handle); if (fd < 0) return TEEC_ERROR_BAD_PARAMETERS; l = sizeof(socktype); if (getsockopt(fd, SOL_SOCKET, SO_TYPE, &socktype, &l)) return TEEC_ERROR_BAD_PARAMETERS; buf = tee_supp_param_to_va(params + 1); switch (socktype) { case SOCK_STREAM: sz = MEMREF_SIZE(params + 1); res = tee_socket_ioctl_tcp(fd, command, buf, &sz); MEMREF_SIZE(params + 1) = sz; return res; case SOCK_DGRAM: sz = MEMREF_SIZE(params + 1); res = tee_socket_ioctl_udp(fd, command, buf, &sz); MEMREF_SIZE(params + 1) = sz; return res; default: return TEEC_ERROR_BAD_PARAMETERS; } } TEEC_Result tee_socket_process(size_t num_params, struct tee_ioctl_param *params) { if (!num_params || !tee_supp_param_is_value(params)) return TEEC_ERROR_BAD_PARAMETERS; switch (params->a) { case OPTEE_MRC_SOCKET_OPEN: return tee_socket_open(num_params, params); case OPTEE_MRC_SOCKET_CLOSE: return tee_socket_close(num_params, params); case OPTEE_MRC_SOCKET_CLOSE_ALL: return tee_socket_close_all(num_params, params); case OPTEE_MRC_SOCKET_SEND: return tee_socket_send(num_params, params); case OPTEE_MRC_SOCKET_RECV: return tee_socket_recv(num_params, params); case OPTEE_MRC_SOCKET_IOCTL: return tee_socket_ioctl(num_params, params); default: return TEEC_ERROR_BAD_PARAMETERS; } }