/* * Copyright (c) 2009 Lukas Mejdrech * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * - 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. * - The name of the author may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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. */ /** @addtogroup libc * @{ */ /** @file * Socket application program interface (API) implementation. * @see socket.h for more information. * This is a part of the network application library. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** Initial received packet queue size. */ #define SOCKET_INITIAL_RECEIVED_SIZE 4 /** Maximum received packet queue size. */ #define SOCKET_MAX_RECEIVED_SIZE 0 /** Initial waiting sockets queue size. */ #define SOCKET_INITIAL_ACCEPTED_SIZE 1 /** Maximum waiting sockets queue size. */ #define SOCKET_MAX_ACCEPTED_SIZE 0 /** Default timeout for connections in microseconds. */ #define SOCKET_CONNECT_TIMEOUT (1 * 1000 * 1000) /** * Maximum number of random attempts to find a new socket identifier before * switching to the sequence. */ #define SOCKET_ID_TRIES 100 /** Type definition of the socket specific data. * @see socket */ typedef struct socket socket_t; /** Type definition of the socket specific data pointer. * @see socket */ typedef socket_t *socket_ref; /** Socket specific data. * * Each socket lock locks only its structure part and any number of them may be * locked simultaneously. */ struct socket { /** Socket identifier. */ int socket_id; /** Parent module phone. */ int phone; /** Parent module service. */ services_t service; /** * Underlying protocol header size. * Sending and receiving optimalization. */ size_t header_size; /** Packet data fragment size. Sending optimization. */ size_t data_fragment_size; /** * Sending safety lock. * Locks the header_size and data_fragment_size attributes. */ fibril_rwlock_t sending_lock; /** Received packets queue. */ dyn_fifo_t received; /** * Received packets safety lock. * Used for receiving and receive notifications. * Locks the received attribute. */ fibril_mutex_t receive_lock; /** Received packets signaling. Signaled upon receive notification. */ fibril_condvar_t receive_signal; /** Waiting sockets queue. */ dyn_fifo_t accepted; /** * Waiting sockets safety lock. * Used for accepting and accept notifications. * Locks the accepted attribute. */ fibril_mutex_t accept_lock; /** Waiting sockets signaling. Signaled upon accept notification. */ fibril_condvar_t accept_signal; /** * The number of blocked functions called. * Used while waiting for the received packets or accepted sockets. */ int blocked; }; /** Sockets map. * Maps socket identifiers to the socket specific data. * @see int_map.h */ INT_MAP_DECLARE(sockets, socket_t); /** Socket client library global data. */ static struct socket_client_globals { /** TCP module phone. */ int tcp_phone; /** UDP module phone. */ int udp_phone; // /** The last socket identifier. // */ // int last_id; /** Active sockets. */ sockets_ref sockets; /** Safety lock. * Write lock is used only for adding or removing sockets. * When locked for writing, no other socket locks need to be locked. * When locked for reading, any other socket locks may be locked. * No socket lock may be locked if this lock is unlocked. */ fibril_rwlock_t lock; } socket_globals = { .tcp_phone = -1, .udp_phone = -1, // .last_id = 0, .sockets = NULL, .lock = { .readers = 0, .writers = 0, .waiters = { .prev = &socket_globals.lock.waiters, /* XXX */ .next = &socket_globals.lock.waiters /* XXX */ } } }; INT_MAP_IMPLEMENT(sockets, socket_t); /** Returns the active sockets. * * @returns The active sockets. */ static sockets_ref socket_get_sockets(void) { if (!socket_globals.sockets) { socket_globals.sockets = (sockets_ref) malloc(sizeof(sockets_t)); if (!socket_globals.sockets) return NULL; if (sockets_initialize(socket_globals.sockets) != EOK) { free(socket_globals.sockets); socket_globals.sockets = NULL; } srand(task_get_id()); } return socket_globals.sockets; } /** Default thread for new connections. * * @param[in] iid The initial message identifier. * @param[in] icall The initial message call structure. */ static void socket_connection(ipc_callid_t iid, ipc_call_t * icall) { ERROR_DECLARE; ipc_callid_t callid; ipc_call_t call; socket_ref socket; loop: callid = async_get_call(&call); switch (IPC_GET_METHOD(call)) { case NET_SOCKET_RECEIVED: case NET_SOCKET_ACCEPTED: case NET_SOCKET_DATA_FRAGMENT_SIZE: fibril_rwlock_read_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), SOCKET_GET_SOCKET_ID(call)); if (!socket) { ERROR_CODE = ENOTSOCK; fibril_rwlock_read_unlock(&socket_globals.lock); break; } switch (IPC_GET_METHOD(call)) { case NET_SOCKET_RECEIVED: fibril_mutex_lock(&socket->receive_lock); // push the number of received packet fragments if (!ERROR_OCCURRED(dyn_fifo_push(&socket->received, SOCKET_GET_DATA_FRAGMENTS(call), SOCKET_MAX_RECEIVED_SIZE))) { // signal the received packet fibril_condvar_signal(&socket->receive_signal); } fibril_mutex_unlock(&socket->receive_lock); break; case NET_SOCKET_ACCEPTED: // push the new socket identifier fibril_mutex_lock(&socket->accept_lock); if (!ERROR_OCCURRED(dyn_fifo_push(&socket->accepted, 1, SOCKET_MAX_ACCEPTED_SIZE))) { // signal the accepted socket fibril_condvar_signal(&socket->accept_signal); } fibril_mutex_unlock(&socket->accept_lock); break; default: ERROR_CODE = ENOTSUP; } if ((SOCKET_GET_DATA_FRAGMENT_SIZE(call) > 0) && (SOCKET_GET_DATA_FRAGMENT_SIZE(call) != socket->data_fragment_size)) { fibril_rwlock_write_lock(&socket->sending_lock); // set the data fragment size socket->data_fragment_size = SOCKET_GET_DATA_FRAGMENT_SIZE(call); fibril_rwlock_write_unlock(&socket->sending_lock); } fibril_rwlock_read_unlock(&socket_globals.lock); break; default: ERROR_CODE = ENOTSUP; } ipc_answer_0(callid, (ipcarg_t) ERROR_CODE); goto loop; } /** Returns the TCP module phone. * * Connects to the TCP module if necessary. * * @returns The TCP module phone. * @returns Other error codes as defined for the * bind_service_timeout() function. */ static int socket_get_tcp_phone(void) { if (socket_globals.tcp_phone < 0) { socket_globals.tcp_phone = bind_service_timeout(SERVICE_TCP, 0, 0, SERVICE_TCP, socket_connection, SOCKET_CONNECT_TIMEOUT); } return socket_globals.tcp_phone; } /** Returns the UDP module phone. * * Connects to the UDP module if necessary. * * @returns The UDP module phone. * @returns Other error codes as defined for the * bind_service_timeout() function. */ static int socket_get_udp_phone(void) { if (socket_globals.udp_phone < 0) { socket_globals.udp_phone = bind_service_timeout(SERVICE_UDP, 0, 0, SERVICE_UDP, socket_connection, SOCKET_CONNECT_TIMEOUT); } return socket_globals.udp_phone; } /** Tries to find a new free socket identifier. * * @returns The new socket identifier. * @returns ELIMIT if there is no socket identifier available. */ static int socket_generate_new_id(void) { sockets_ref sockets; int socket_id = 0; int count; sockets = socket_get_sockets(); count = 0; // socket_id = socket_globals.last_id; do { if (count < SOCKET_ID_TRIES) { socket_id = rand() % INT_MAX; ++count; } else if (count == SOCKET_ID_TRIES) { socket_id = 1; ++count; // only this branch for last_id } else { if (socket_id < INT_MAX) { ++socket_id; /* } else if(socket_globals.last_id) { * socket_globals.last_id = 0; * socket_id = 1; */ } else { return ELIMIT; } } } while (sockets_find(sockets, socket_id)); // last_id = socket_id return socket_id; } /** Initializes a new socket specific data. * * @param[in,out] socket The socket to be initialized. * @param[in] socket_id The new socket identifier. * @param[in] phone The parent module phone. * @param[in] service The parent module service. */ static void socket_initialize(socket_ref socket, int socket_id, int phone, services_t service) { socket->socket_id = socket_id; socket->phone = phone; socket->service = service; dyn_fifo_initialize(&socket->received, SOCKET_INITIAL_RECEIVED_SIZE); dyn_fifo_initialize(&socket->accepted, SOCKET_INITIAL_ACCEPTED_SIZE); fibril_mutex_initialize(&socket->receive_lock); fibril_condvar_initialize(&socket->receive_signal); fibril_mutex_initialize(&socket->accept_lock); fibril_condvar_initialize(&socket->accept_signal); fibril_rwlock_initialize(&socket->sending_lock); } /** Creates a new socket. * * @param[in] domain The socket protocol family. * @param[in] type Socket type. * @param[in] protocol Socket protocol. * @returns The socket identifier on success. * @returns EPFNOTSUPPORT if the protocol family is not supported. * @returns ESOCKNOTSUPPORT if the socket type is not supported. * @returns EPROTONOSUPPORT if the protocol is not supported. * @returns ENOMEM if there is not enough memory left. * @returns ELIMIT if there was not a free socket identifier found * this time. * @returns Other error codes as defined for the NET_SOCKET message. * @returns Other error codes as defined for the * bind_service_timeout() function. */ int socket(int domain, int type, int protocol) { ERROR_DECLARE; socket_ref socket; int phone; int socket_id; services_t service; ipcarg_t fragment_size; ipcarg_t header_size; // find the appropriate service switch (domain) { case PF_INET: switch (type) { case SOCK_STREAM: if (!protocol) protocol = IPPROTO_TCP; switch (protocol) { case IPPROTO_TCP: phone = socket_get_tcp_phone(); service = SERVICE_TCP; break; default: return EPROTONOSUPPORT; } break; case SOCK_DGRAM: if (!protocol) protocol = IPPROTO_UDP; switch (protocol) { case IPPROTO_UDP: phone = socket_get_udp_phone(); service = SERVICE_UDP; break; default: return EPROTONOSUPPORT; } break; case SOCK_RAW: default: return ESOCKTNOSUPPORT; } break; case PF_INET6: default: return EPFNOSUPPORT; } if (phone < 0) return phone; // create a new socket structure socket = (socket_ref) malloc(sizeof(socket_t)); if (!socket) return ENOMEM; bzero(socket, sizeof(*socket)); fibril_rwlock_write_lock(&socket_globals.lock); // request a new socket socket_id = socket_generate_new_id(); if (socket_id <= 0) { fibril_rwlock_write_unlock(&socket_globals.lock); free(socket); return socket_id; } if (ERROR_OCCURRED((int) async_req_3_3(phone, NET_SOCKET, socket_id, 0, service, NULL, &fragment_size, &header_size))) { fibril_rwlock_write_unlock(&socket_globals.lock); free(socket); return ERROR_CODE; } socket->data_fragment_size = (size_t) fragment_size; socket->header_size = (size_t) header_size; // finish the new socket initialization socket_initialize(socket, socket_id, phone, service); // store the new socket ERROR_CODE = sockets_add(socket_get_sockets(), socket_id, socket); fibril_rwlock_write_unlock(&socket_globals.lock); if (ERROR_CODE < 0) { dyn_fifo_destroy(&socket->received); dyn_fifo_destroy(&socket->accepted); free(socket); async_msg_3(phone, NET_SOCKET_CLOSE, (ipcarg_t) socket_id, 0, service); return ERROR_CODE; } return socket_id; } /** Sends message to the socket parent module with specified data. * * @param[in] socket_id Socket identifier. * @param[in] message The action message. * @param[in] arg2 The second message parameter. * @param[in] data The data to be sent. * @param[in] datalength The data length. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data parameter is NULL. * @returns NO_DATA if the datalength parameter is zero (0). * @returns Other error codes as defined for the spcific message. */ static int socket_send_data(int socket_id, ipcarg_t message, ipcarg_t arg2, const void *data, size_t datalength) { socket_ref socket; aid_t message_id; ipcarg_t result; if (!data) return EBADMEM; if (!datalength) return NO_DATA; fibril_rwlock_read_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_read_unlock(&socket_globals.lock); return ENOTSOCK; } // request the message message_id = async_send_3(socket->phone, message, (ipcarg_t) socket->socket_id, arg2, socket->service, NULL); // send the address async_data_write_start(socket->phone, data, datalength); fibril_rwlock_read_unlock(&socket_globals.lock); async_wait_for(message_id, &result); return (int) result; } /** Binds the socket to a port address. * * @param[in] socket_id Socket identifier. * @param[in] my_addr The port address. * @param[in] addrlen The address length. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the my_addr parameter is NULL. * @returns NO_DATA if the addlen parameter is zero. * @returns Other error codes as defined for the NET_SOCKET_BIND * message. */ int bind(int socket_id, const struct sockaddr * my_addr, socklen_t addrlen) { if (addrlen <= 0) return EINVAL; // send the address return socket_send_data(socket_id, NET_SOCKET_BIND, 0, my_addr, (size_t) addrlen); } /** Sets the number of connections waiting to be accepted. * * @param[in] socket_id Socket identifier. * @param[in] backlog The maximum number of waiting sockets to be accepted. * @returns EOK on success. * @returns EINVAL if the backlog parameter is not positive (<=0). * @returns ENOTSOCK if the socket is not found. * @returns Other error codes as defined for the NET_SOCKET_LISTEN * message. */ int listen(int socket_id, int backlog) { socket_ref socket; int result; if (backlog <= 0) return EINVAL; fibril_rwlock_read_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_read_unlock(&socket_globals.lock); return ENOTSOCK; } // request listen backlog change result = (int) async_req_3_0(socket->phone, NET_SOCKET_LISTEN, (ipcarg_t) socket->socket_id, (ipcarg_t) backlog, socket->service); fibril_rwlock_read_unlock(&socket_globals.lock); return result; } /** Accepts waiting socket. * * Blocks until such a socket exists. * * @param[in] socket_id Socket identifier. * @param[out] cliaddr The remote client address. * @param[in] addrlen The address length. * @returns EOK on success. * @returns EBADMEM if the cliaddr or addrlen parameter is NULL. * @returns EINVAL if the backlog parameter is not positive (<=0). * @returns ENOTSOCK if the socket is not found. * @returns Other error codes as defined for the NET_SOCKET_ACCEPT * message. */ int accept(int socket_id, struct sockaddr * cliaddr, socklen_t * addrlen) { socket_ref socket; socket_ref new_socket; aid_t message_id; ipcarg_t ipc_result; int result; ipc_call_t answer; if (!cliaddr || !addrlen) return EBADMEM; fibril_rwlock_write_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_write_unlock(&socket_globals.lock); return ENOTSOCK; } fibril_mutex_lock(&socket->accept_lock); // wait for an accepted socket ++ socket->blocked; while (dyn_fifo_value(&socket->accepted) <= 0) { fibril_rwlock_write_unlock(&socket_globals.lock); fibril_condvar_wait(&socket->accept_signal, &socket->accept_lock); // drop the accept lock to avoid deadlock fibril_mutex_unlock(&socket->accept_lock); fibril_rwlock_write_lock(&socket_globals.lock); fibril_mutex_lock(&socket->accept_lock); } -- socket->blocked; // create a new scoket new_socket = (socket_ref) malloc(sizeof(socket_t)); if (!new_socket) { fibril_mutex_unlock(&socket->accept_lock); fibril_rwlock_write_unlock(&socket_globals.lock); return ENOMEM; } bzero(new_socket, sizeof(*new_socket)); socket_id = socket_generate_new_id(); if (socket_id <= 0) { fibril_mutex_unlock(&socket->accept_lock); fibril_rwlock_write_unlock(&socket_globals.lock); free(new_socket); return socket_id; } socket_initialize(new_socket, socket_id, socket->phone, socket->service); result = sockets_add(socket_get_sockets(), new_socket->socket_id, new_socket); if (result < 0) { fibril_mutex_unlock(&socket->accept_lock); fibril_rwlock_write_unlock(&socket_globals.lock); free(new_socket); return result; } // request accept message_id = async_send_5(socket->phone, NET_SOCKET_ACCEPT, (ipcarg_t) socket->socket_id, 0, socket->service, 0, new_socket->socket_id, &answer); // read address ipc_data_read_start(socket->phone, cliaddr, *addrlen); fibril_rwlock_write_unlock(&socket_globals.lock); async_wait_for(message_id, &ipc_result); result = (int) ipc_result; if (result > 0) { if (result != socket_id) result = EINVAL; // dequeue the accepted socket if successful dyn_fifo_pop(&socket->accepted); // set address length *addrlen = SOCKET_GET_ADDRESS_LENGTH(answer); new_socket->data_fragment_size = SOCKET_GET_DATA_FRAGMENT_SIZE(answer); } else if (result == ENOTSOCK) { // empty the queue if no accepted sockets while (dyn_fifo_pop(&socket->accepted) > 0) ; } fibril_mutex_unlock(&socket->accept_lock); return result; } /** Connects socket to the remote server. * * @param[in] socket_id Socket identifier. * @param[in] serv_addr The remote server address. * @param[in] addrlen The address length. * @returns EOK on success. * @returns EBADMEM if the serv_addr parameter is NULL. * @returns NO_DATA if the addlen parameter is zero. * @returns ENOTSOCK if the socket is not found. * @returns Other error codes as defined for the NET_SOCKET_CONNECT * message. */ int connect(int socket_id, const struct sockaddr *serv_addr, socklen_t addrlen) { if (!serv_addr) return EDESTADDRREQ; if (!addrlen) return EDESTADDRREQ; // send the address return socket_send_data(socket_id, NET_SOCKET_CONNECT, 0, serv_addr, addrlen); } /** Clears and destroys the socket. * * @param[in] socket The socket to be destroyed. */ static void socket_destroy(socket_ref socket) { int accepted_id; // destroy all accepted sockets while ((accepted_id = dyn_fifo_pop(&socket->accepted)) >= 0) socket_destroy(sockets_find(socket_get_sockets(), accepted_id)); dyn_fifo_destroy(&socket->received); dyn_fifo_destroy(&socket->accepted); sockets_exclude(socket_get_sockets(), socket->socket_id); } /** Closes the socket. * * @param[in] socket_id Socket identifier. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EINPROGRESS if there is another blocking function in * progress. * @returns Other error codes as defined for the NET_SOCKET_CLOSE * message. */ int closesocket(int socket_id) { ERROR_DECLARE; socket_ref socket; fibril_rwlock_write_lock(&socket_globals.lock); socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_write_unlock(&socket_globals.lock); return ENOTSOCK; } if (socket->blocked) { fibril_rwlock_write_unlock(&socket_globals.lock); return EINPROGRESS; } // request close ERROR_PROPAGATE((int) async_req_3_0(socket->phone, NET_SOCKET_CLOSE, (ipcarg_t) socket->socket_id, 0, socket->service)); // free the socket structure socket_destroy(socket); fibril_rwlock_write_unlock(&socket_globals.lock); return EOK; } /** Sends data via the socket to the remote address. * * Binds the socket to a free port if not already connected/bound. * * @param[in] message The action message. * @param[in] socket_id Socket identifier. * @param[in] data The data to be sent. * @param[in] datalength The data length. * @param[in] flags Various send flags. * @param[in] toaddr The destination address. May be NULL for connected * sockets. * @param[in] addrlen The address length. Used only if toaddr is not NULL. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data or toaddr parameter is NULL. * @returns NO_DATA if the datalength or the addrlen parameter is * zero (0). * @returns Other error codes as defined for the NET_SOCKET_SENDTO * message. */ static int sendto_core(ipcarg_t message, int socket_id, const void *data, size_t datalength, int flags, const struct sockaddr *toaddr, socklen_t addrlen) { socket_ref socket; aid_t message_id; ipcarg_t result; size_t fragments; ipc_call_t answer; if (!data) return EBADMEM; if (!datalength) return NO_DATA; fibril_rwlock_read_lock(&socket_globals.lock); // find socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_read_unlock(&socket_globals.lock); return ENOTSOCK; } fibril_rwlock_read_lock(&socket->sending_lock); // compute data fragment count if (socket->data_fragment_size > 0) { fragments = (datalength + socket->header_size) / socket->data_fragment_size; if ((datalength + socket->header_size) % socket->data_fragment_size) ++fragments; } else { fragments = 1; } // request send message_id = async_send_5(socket->phone, message, (ipcarg_t) socket->socket_id, (fragments == 1 ? datalength : socket->data_fragment_size), socket->service, (ipcarg_t) flags, fragments, &answer); // send the address if given if (!toaddr || (async_data_write_start(socket->phone, toaddr, addrlen) == EOK)) { if (fragments == 1) { // send all if only one fragment async_data_write_start(socket->phone, data, datalength); } else { // send the first fragment async_data_write_start(socket->phone, data, socket->data_fragment_size - socket->header_size); data = ((const uint8_t *) data) + socket->data_fragment_size - socket->header_size; // send the middle fragments while (--fragments > 1) { async_data_write_start(socket->phone, data, socket->data_fragment_size); data = ((const uint8_t *) data) + socket->data_fragment_size; } // send the last fragment async_data_write_start(socket->phone, data, (datalength + socket->header_size) % socket->data_fragment_size); } } async_wait_for(message_id, &result); if ((SOCKET_GET_DATA_FRAGMENT_SIZE(answer) > 0) && (SOCKET_GET_DATA_FRAGMENT_SIZE(answer) != socket->data_fragment_size)) { // set the data fragment size socket->data_fragment_size = SOCKET_GET_DATA_FRAGMENT_SIZE(answer); } fibril_rwlock_read_unlock(&socket->sending_lock); fibril_rwlock_read_unlock(&socket_globals.lock); return (int) result; } /** Sends data via the socket. * * @param[in] socket_id Socket identifier. * @param[in] data The data to be sent. * @param[in] datalength The data length. * @param[in] flags Various send flags. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data parameter is NULL. * @returns NO_DATA if the datalength parameter is zero. * @returns Other error codes as defined for the NET_SOCKET_SEND * message. */ int send(int socket_id, void *data, size_t datalength, int flags) { // without the address return sendto_core(NET_SOCKET_SEND, socket_id, data, datalength, flags, NULL, 0); } /** Sends data via the socket to the remote address. * * Binds the socket to a free port if not already connected/bound. * * @param[in] socket_id Socket identifier. * @param[in] data The data to be sent. * @param[in] datalength The data length. * @param[in] flags Various send flags. * @param[in] toaddr The destination address. * @param[in] addrlen The address length. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data or toaddr parameter is NULL. * @returns NO_DATA if the datalength or the addrlen parameter is * zero. * @returns Other error codes as defined for the NET_SOCKET_SENDTO * message. */ int sendto(int socket_id, const void *data, size_t datalength, int flags, const struct sockaddr *toaddr, socklen_t addrlen) { if (!toaddr) return EDESTADDRREQ; if (!addrlen) return EDESTADDRREQ; // with the address return sendto_core(NET_SOCKET_SENDTO, socket_id, data, datalength, flags, toaddr, addrlen); } /** Receives data via the socket. * * @param[in] message The action message. * @param[in] socket_id Socket identifier. * @param[out] data The data buffer to be filled. * @param[in] datalength The data length. * @param[in] flags Various receive flags. * @param[out] fromaddr The source address. May be NULL for connected sockets. * @param[in,out] addrlen The address length. The maximum address length is * read. The actual address length is set. Used only if * fromaddr is not NULL. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data parameter is NULL. * @returns NO_DATA if the datalength or addrlen parameter is zero. * @returns Other error codes as defined for the spcific message. */ static int recvfrom_core(ipcarg_t message, int socket_id, void *data, size_t datalength, int flags, struct sockaddr *fromaddr, socklen_t *addrlen) { socket_ref socket; aid_t message_id; ipcarg_t ipc_result; int result; size_t fragments; size_t *lengths; size_t index; ipc_call_t answer; if (!data) return EBADMEM; if (!datalength) return NO_DATA; if (fromaddr && !addrlen) return EINVAL; fibril_rwlock_read_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_read_unlock(&socket_globals.lock); return ENOTSOCK; } fibril_mutex_lock(&socket->receive_lock); // wait for a received packet ++socket->blocked; while ((result = dyn_fifo_value(&socket->received)) <= 0) { fibril_rwlock_read_unlock(&socket_globals.lock); fibril_condvar_wait(&socket->receive_signal, &socket->receive_lock); // drop the receive lock to avoid deadlock fibril_mutex_unlock(&socket->receive_lock); fibril_rwlock_read_lock(&socket_globals.lock); fibril_mutex_lock(&socket->receive_lock); } --socket->blocked; fragments = (size_t) result; // prepare lengths if more fragments if (fragments > 1) { lengths = (size_t *) malloc(sizeof(size_t) * fragments + sizeof(size_t)); if (!lengths) { fibril_mutex_unlock(&socket->receive_lock); fibril_rwlock_read_unlock(&socket_globals.lock); return ENOMEM; } // request packet data message_id = async_send_4(socket->phone, message, (ipcarg_t) socket->socket_id, 0, socket->service, (ipcarg_t) flags, &answer); // read the address if desired if(!fromaddr || (async_data_read_start(socket->phone, fromaddr, *addrlen) == EOK)) { // read the fragment lengths if (async_data_read_start(socket->phone, lengths, sizeof(int) * (fragments + 1)) == EOK) { if (lengths[fragments] <= datalength) { // read all fragments if long enough for (index = 0; index < fragments; ++index) { async_data_read_start( socket->phone, data, lengths[index]); data = ((uint8_t *) data) + lengths[index]; } } } } free(lengths); } else { // request packet data message_id = async_send_4(socket->phone, message, (ipcarg_t) socket->socket_id, 0, socket->service, (ipcarg_t) flags, &answer); // read the address if desired if (!fromaddr || (async_data_read_start(socket->phone, fromaddr, *addrlen) == EOK)) { // read all if only one fragment async_data_read_start(socket->phone, data, datalength); } } async_wait_for(message_id, &ipc_result); result = (int) ipc_result; if (result == EOK) { // dequeue the received packet dyn_fifo_pop(&socket->received); // return read data length result = SOCKET_GET_READ_DATA_LENGTH(answer); // set address length if (fromaddr && addrlen) *addrlen = SOCKET_GET_ADDRESS_LENGTH(answer); } fibril_mutex_unlock(&socket->receive_lock); fibril_rwlock_read_unlock(&socket_globals.lock); return result; } /** Receives data via the socket. * * @param[in] socket_id Socket identifier. * @param[out] data The data buffer to be filled. * @param[in] datalength The data length. * @param[in] flags Various receive flags. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data parameter is NULL. * @returns NO_DATA if the datalength parameter is zero. * @returns Other error codes as defined for the NET_SOCKET_RECV * message. */ int recv(int socket_id, void *data, size_t datalength, int flags) { // without the address return recvfrom_core(NET_SOCKET_RECV, socket_id, data, datalength, flags, NULL, NULL); } /** Receives data via the socket. * * @param[in] socket_id Socket identifier. * @param[out] data The data buffer to be filled. * @param[in] datalength The data length. * @param[in] flags Various receive flags. * @param[out] fromaddr The source address. * @param[in,out] addrlen The address length. The maximum address length is * read. The actual address length is set. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the data or fromaddr parameter is NULL. * @returns NO_DATA if the datalength or addrlen parameter is zero. * @returns Other error codes as defined for the NET_SOCKET_RECVFROM * message. */ int recvfrom(int socket_id, void *data, size_t datalength, int flags, struct sockaddr *fromaddr, socklen_t *addrlen) { if (!fromaddr) return EBADMEM; if (!addrlen) return NO_DATA; // with the address return recvfrom_core(NET_SOCKET_RECVFROM, socket_id, data, datalength, flags, fromaddr, addrlen); } /** Gets socket option. * * @param[in] socket_id Socket identifier. * @param[in] level The socket options level. * @param[in] optname The socket option to be get. * @param[out] value The value buffer to be filled. * @param[in,out] optlen The value buffer length. The maximum length is read. * The actual length is set. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the value or optlen parameter is NULL. * @returns NO_DATA if the optlen parameter is zero. * @returns Other error codes as defined for the * NET_SOCKET_GETSOCKOPT message. */ int getsockopt(int socket_id, int level, int optname, void *value, size_t *optlen) { socket_ref socket; aid_t message_id; ipcarg_t result; if (!value || !optlen) return EBADMEM; if (!*optlen) return NO_DATA; fibril_rwlock_read_lock(&socket_globals.lock); // find the socket socket = sockets_find(socket_get_sockets(), socket_id); if (!socket) { fibril_rwlock_read_unlock(&socket_globals.lock); return ENOTSOCK; } // request option value message_id = async_send_3(socket->phone, NET_SOCKET_GETSOCKOPT, (ipcarg_t) socket->socket_id, (ipcarg_t) optname, socket->service, NULL); // read the length if (async_data_read_start(socket->phone, optlen, sizeof(*optlen)) == EOK) { // read the value async_data_read_start(socket->phone, value, *optlen); } fibril_rwlock_read_unlock(&socket_globals.lock); async_wait_for(message_id, &result); return (int) result; } /** Sets socket option. * * @param[in] socket_id Socket identifier. * @param[in] level The socket options level. * @param[in] optname The socket option to be set. * @param[in] value The value to be set. * @param[in] optlen The value length. * @returns EOK on success. * @returns ENOTSOCK if the socket is not found. * @returns EBADMEM if the value parameter is NULL. * @returns NO_DATA if the optlen parameter is zero. * @returns Other error codes as defined for the * NET_SOCKET_SETSOCKOPT message. */ int setsockopt(int socket_id, int level, int optname, const void *value, size_t optlen) { // send the value return socket_send_data(socket_id, NET_SOCKET_SETSOCKOPT, (ipcarg_t) optname, value, optlen); } /** @} */