diff --git a/core/net/ip/tcp-socket.c b/core/net/ip/tcp-socket.c new file mode 100644 index 000000000..f914ae206 --- /dev/null +++ b/core/net/ip/tcp-socket.c @@ -0,0 +1,365 @@ +/* + * Copyright (c) 2012-2014, Thingsquare, http://www.thingsquare.com/. + * 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. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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 "contiki.h" +#include "contiki-net.h" + +#include "lib/list.h" + +#include "tcp-socket.h" + +#include +#include + +#define MIN(a, b) ((a) < (b) ? (a) : (b)) + +LIST(socketlist); +/*---------------------------------------------------------------------------*/ +PROCESS(tcp_socket_process, "TCP socket process"); +/*---------------------------------------------------------------------------*/ +static void +call_event(struct tcp_socket *s, tcp_socket_event_t event) +{ + if(s != NULL && s->event_callback != NULL) { + s->event_callback(s, s->ptr, event); + } +} +/*---------------------------------------------------------------------------*/ +static void +senddata(struct tcp_socket *s) +{ + int len; + + if(s->output_data_len > 0) { + len = MIN(s->output_data_len, uip_mss()); + s->output_data_send_nxt = len; + uip_send(s->output_data_ptr, len); + } +} +/*---------------------------------------------------------------------------*/ +static void +acked(struct tcp_socket *s) +{ + if(s->output_data_len > 0) { + /* Copy the data in the outputbuf down and update outputbufptr and + outputbuf_lastsent */ + + if(s->output_data_send_nxt > 0) { + memcpy(&s->output_data_ptr[0], + &s->output_data_ptr[s->output_data_send_nxt], + s->output_data_maxlen - s->output_data_send_nxt); + } + if(s->output_data_len < s->output_data_send_nxt) { + printf("tcp: acked assertion failed s->output_data_len (%d) < s->output_data_send_nxt (%d)\n", + s->output_data_len, + s->output_data_send_nxt); + } + s->output_data_len -= s->output_data_send_nxt; + s->output_data_send_nxt = 0; + + call_event(s, TCP_SOCKET_DATA_SENT); + } +} +/*---------------------------------------------------------------------------*/ +static void +newdata(struct tcp_socket *s) +{ + uint16_t len, copylen, bytesleft; + uint8_t *dataptr; + len = uip_datalen(); + dataptr = uip_appdata; + + /* We have a segment with data coming in. We copy as much data as + possible into the input buffer and call the input callback + function. The input callback returns the number of bytes that + should be retained in the buffer, or zero if all data should be + consumed. If there is data to be retained, the highest bytes of + data are copied down into the input buffer. */ + do { + copylen = MIN(len, s->input_data_maxlen); + memcpy(s->input_data_ptr, dataptr, copylen); + if(s->input_callback) { + bytesleft = s->input_callback(s, s->ptr, + s->input_data_ptr, copylen); + } else { + bytesleft = 0; + } + if(bytesleft > 0) { + printf("tcp: newdata, bytesleft > 0 (%d) not implemented\n", bytesleft); + } + dataptr += copylen; + len -= copylen; + + } while(len > 0); +} +/*---------------------------------------------------------------------------*/ +static void +relisten(struct tcp_socket *s) +{ + if(s != NULL && s->listen_port != 0) { + s->flags |= TCP_SOCKET_FLAGS_LISTENING; + } +} +/*---------------------------------------------------------------------------*/ +static void +appcall(void *state) +{ + struct tcp_socket *s = state; + + if(uip_connected()) { + /* Check if this connection originated in a local listen + socket. We do this by checking the state pointer - if NULL, + this is an incoming listen connection. If so, we need to + connect the socket to the uip_conn and call the event + function. */ + if(s == NULL) { + for(s = list_head(socketlist); + s != NULL; + s = list_item_next(s)) { + if((s->flags & TCP_SOCKET_FLAGS_LISTENING) != 0 && + s->listen_port != 0 && + s->listen_port == uip_htons(uip_conn->lport)) { + s->flags &= ~TCP_SOCKET_FLAGS_LISTENING; + tcp_markconn(uip_conn, s); + call_event(s, TCP_SOCKET_CONNECTED); + break; + } + } + } else { + call_event(s, TCP_SOCKET_CONNECTED); + } + + if(s == NULL) { + uip_abort(); + } else { + if(uip_newdata()) { + newdata(s); + } + senddata(s); + } + return; + } + + if(uip_timedout()) { + call_event(s, TCP_SOCKET_TIMEDOUT); + relisten(s); + } + + if(uip_aborted()) { + call_event(s, TCP_SOCKET_ABORTED); + relisten(s); + } + + if(s == NULL) { + uip_abort(); + return; + } + + if(uip_acked()) { + acked(s); + } + if(uip_newdata()) { + newdata(s); + } + + if(uip_rexmit() || + uip_newdata() || + uip_acked()) { + senddata(s); + } else if(uip_poll()) { + senddata(s); + } + + if(s->output_data_len == 0 && s->flags & TCP_SOCKET_FLAGS_CLOSING) { + s->flags &= ~TCP_SOCKET_FLAGS_CLOSING; + uip_close(); + tcp_markconn(uip_conn, NULL); + call_event(s, TCP_SOCKET_CLOSED); + relisten(s); + } + + if(uip_closed()) { + tcp_markconn(uip_conn, NULL); + call_event(s, TCP_SOCKET_CLOSED); + relisten(s); + } +} +/*---------------------------------------------------------------------------*/ +PROCESS_THREAD(tcp_socket_process, ev, data) +{ + PROCESS_BEGIN(); + while(1) { + PROCESS_WAIT_EVENT(); + + if(ev == tcpip_event) { + appcall(data); + } + } + PROCESS_END(); +} +/*---------------------------------------------------------------------------*/ +static void +init(void) +{ + static uint8_t inited = 0; + if(!inited) { + list_init(socketlist); + process_start(&tcp_socket_process, NULL); + inited = 1; + } +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_register(struct tcp_socket *s, void *ptr, + uint8_t *input_databuf, int input_databuf_len, + uint8_t *output_databuf, int output_databuf_len, + tcp_socket_data_callback_t input_callback, + tcp_socket_event_callback_t event_callback) +{ + + init(); + + if(s == NULL) { + return -1; + } + s->ptr = ptr; + s->input_data_ptr = input_databuf; + s->input_data_maxlen = input_databuf_len; + s->output_data_ptr = output_databuf; + s->output_data_maxlen = output_databuf_len; + s->input_callback = input_callback; + s->event_callback = event_callback; + list_add(socketlist, s); + + s->listen_port = 0; + s->flags = TCP_SOCKET_FLAGS_NONE; + return 1; +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_connect(struct tcp_socket *s, + uip_ipaddr_t *ipaddr, + uint16_t port) +{ + if(s == NULL) { + return -1; + } + PROCESS_CONTEXT_BEGIN(&tcp_socket_process); + s->c = tcp_connect(ipaddr, uip_htons(port), s); + PROCESS_CONTEXT_END(); + if(s->c == NULL) { + return -1; + } else { + return 1; + } +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_listen(struct tcp_socket *s, + uint16_t port) +{ + if(s == NULL) { + return -1; + } + + s->listen_port = port; + PROCESS_CONTEXT_BEGIN(&tcp_socket_process); + tcp_listen(uip_htons(port)); + PROCESS_CONTEXT_END(); + s->flags |= TCP_SOCKET_FLAGS_LISTENING; + return 1; +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_unlisten(struct tcp_socket *s) +{ + if(s == NULL) { + return -1; + } + + PROCESS_CONTEXT_BEGIN(&tcp_socket_process); + tcp_unlisten(uip_htons(s->listen_port)); + PROCESS_CONTEXT_END(); + s->listen_port = 0; + s->flags &= ~TCP_SOCKET_FLAGS_LISTENING; + return 1; +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_send(struct tcp_socket *s, + const uint8_t *data, int datalen) +{ + int len; + + if(s == NULL) { + return -1; + } + + len = MIN(datalen, s->output_data_maxlen - s->output_data_len); + + memcpy(&s->output_data_ptr[s->output_data_len], data, len); + s->output_data_len += len; + return len; +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_send_str(struct tcp_socket *s, + const char *str) +{ + return tcp_socket_send(s, (const uint8_t *)str, strlen(str)); +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_close(struct tcp_socket *s) +{ + if(s == NULL) { + return -1; + } + + s->flags |= TCP_SOCKET_FLAGS_CLOSING; + return 1; +} +/*---------------------------------------------------------------------------*/ +int +tcp_socket_unregister(struct tcp_socket *s) +{ + if(s == NULL) { + return -1; + } + + tcp_socket_unlisten(s); + if(s->c != NULL) { + tcp_attach(s->c, NULL); + } + list_remove(socketlist, s); + return 1; +} +/*---------------------------------------------------------------------------*/ diff --git a/core/net/ip/tcp-socket.h b/core/net/ip/tcp-socket.h new file mode 100644 index 000000000..aea678049 --- /dev/null +++ b/core/net/ip/tcp-socket.h @@ -0,0 +1,268 @@ +/* + * Copyright (c) 2012-2014, Thingsquare, http://www.thingsquare.com/. + * 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. + * 3. Neither the name of the copyright holder nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * 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. + * + */ + +#ifndef TCP_SOCKET_H +#define TCP_SOCKET_H + +struct tcp_socket; + +typedef enum { + TCP_SOCKET_CONNECTED, + TCP_SOCKET_CLOSED, + TCP_SOCKET_TIMEDOUT, + TCP_SOCKET_ABORTED, + TCP_SOCKET_DATA_SENT +} tcp_socket_event_t; + +/** + * \brief TCP data callback function + * \param s A pointer to a TCP socket + * \param ptr A user-defined pointer + * \param input_data_ptr A pointer to the incoming data + * \param input_data_len The length of the incoming data + * \return The function should return the number of bytes to leave in the input buffer + * + * The TCP socket input callback function gets + * called whenever there is new data on the socket. The + * function can choose to either consume the data + * directly, or leave it in the buffer for later. The + * function must return the amount of data to leave in the + * buffer. I.e., if the callback function consumes all + * incoming data, it should return 0. + */ +typedef int (* tcp_socket_data_callback_t)(struct tcp_socket *s, + void *ptr, + const uint8_t *input_data_ptr, + int input_data_len); + + +/** + * \brief TCP event callback function + * \param s A pointer to a TCP socket + * \param ptr A user-defined pointer + * \param event The event number + * + * The TCP socket event callback function gets + * called whenever there is an event on a socket, such as + * the socket getting connected or closed. + */ +typedef void (* tcp_socket_event_callback_t)(struct tcp_socket *s, + void *ptr, + tcp_socket_event_t event); + +struct tcp_socket { + struct tcp_socket *next; + + tcp_socket_data_callback_t input_callback; + tcp_socket_event_callback_t event_callback; + void *ptr; + + struct process *p; + + uint8_t *input_data_ptr; + uint8_t *output_data_ptr; + + uint16_t input_data_maxlen; + uint16_t input_data_len; + uint16_t output_data_maxlen; + uint16_t output_data_len; + uint16_t output_data_send_nxt; + + uint8_t flags; + uint16_t listen_port; + struct uip_conn *c; +}; + +enum { + TCP_SOCKET_FLAGS_NONE = 0x00, + TCP_SOCKET_FLAGS_LISTENING = 0x01, + TCP_SOCKET_FLAGS_CLOSING = 0x02, +}; + +/** + * \brief Register a TCP socket + * \param s A pointer to a TCP socket + * \param ptr A user-defined pointer that will be sent to callbacks for this socket + * \param input_databuf A pointer to a memory area this socket will use for input data + * \param input_databuf_len The size of the input data buffer + * \param output_databuf A pointer to a memory area this socket will use for outgoing data + * \param output_databuf_len The size of the output data buffer + * \param data_callback A pointer to the data callback function for this socket + * \param event_callback A pointer to the event callback function for this socket + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function registers a TCP socket. The function sets + * up the output and input buffers for the socket and + * callback pointers. + * + * TCP sockets use input and output buffers for incoming + * and outgoing data. The memory for these buffers must be + * allocated by the caller. The size of the buffers + * determine the amount of data that can be received and + * sent, and the principle is that the application that + * sets up the TCP socket will know roughly how large + * these buffers should be. The rule of thumb is that the + * input buffer should be large enough to hold the largest + * application layer message that the application will + * receive and the output buffer should be large enough to + * hold the largest application layer message the + * application will send. + * + * TCP throttles incoming data so that if the input buffer + * is filled, the connection will halt until the + * application has read out the data from the input + * buffer. + * + */ +int tcp_socket_register(struct tcp_socket *s, void *ptr, + uint8_t *input_databuf, int input_databuf_len, + uint8_t *output_databuf, int output_databuf_len, + tcp_socket_data_callback_t data_callback, + tcp_socket_event_callback_t event_callback); + +/** + * \brief Connect a TCP socket to a remote host + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \param ipaddr The IP address of the remote host + * \param port The TCP port number, in host byte order, of the remote host + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function connects a TCP socket to a remote host. + * + * When the socket has connected, the event callback will + * get called with the TCP_SOCKET_CONNECTED event. If the + * remote host does not accept the connection, the + * TCP_SOCKET_ABORTED will be sent to the callback. If the + * connection times out before conecting to the remote + * host, the TCP_SOCKET_TIMEDOUT event is sent to the + * callback. + * + */ +int tcp_socket_connect(struct tcp_socket *s, + uip_ipaddr_t *ipaddr, + uint16_t port); + +/** + * \brief Start listening on a specific port + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \param port The TCP port number, in host byte order, of the remote host + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function causes the TCP socket to start listening + * on the given TCP port. + * + * Several sockets can listen on the same port. If a + * remote host connects to the port, one of the listening + * sockets will get connected and the event callback will + * be called with the TCP_SOCKET_CONNECTED event. When the + * connection closes, the socket will go back to listening + * for new connections. + * + */ +int tcp_socket_listen(struct tcp_socket *s, + uint16_t port); + +/** + * \brief Stop listening for new connections + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function causes a listening TCP socket to stop + * listen. The socket must previously been put into listen + * mode with tcp_socket_listen(). + * + */ +int tcp_socket_unlisten(struct tcp_socket *s); + +/** + * \brief Send data on a connected TCP socket + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \param dataptr A pointer to the data to be sent + * \param datalen The length of the data to be sent + * \retval -1 If an error occurs + * \return The number of bytes that were successfully sent + * + * This function sends data over a connected TCP + * socket. The data is placed in the output buffer and + * sent to the remote host as soon as possiblce. When the + * data has been acknowledged by the remote host, the + * event callback is sent with the TCP_SOCKET_DATA_SENT + * event. + */ +int tcp_socket_send(struct tcp_socket *s, + const uint8_t *dataptr, + int datalen); + +/** + * \brief Send a string on a connected TCP socket + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \param strptr A pointer to the string to be sent + * \retval -1 If an error occurs + * \return The number of bytes that were successfully sent + * + * This is a convenience function for sending strings on a + * TCP socket. The function calls tcp_socket_send() to + * send the string. + */ +int tcp_socket_send_str(struct tcp_socket *s, + const char *strptr); + +/** + * \brief Close a connected TCP socket + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function closes a connected TCP socket. When the + * socket has been successfully closed, the event callback + * is called with the TCP_SOCKET_CLOSED event. + * + */ +int tcp_socket_close(struct tcp_socket *s); + +/** + * \brief Unregister a registered socket + * \param s A pointer to a TCP socket that must have been previously registered with tcp_socket_register() + * \retval -1 If an error occurs + * \retval 1 If the operation succeeds. + * + * This function unregisters a previously registered + * socket. This must be done if the process will be + * unloaded from memory. If the TCP socket is connected, + * the connection will be reset. + * + */ +int tcp_socket_unregister(struct tcp_socket *s); +#endif /* TCP_SOCKET_H */