The TCP socket API code

This commit is contained in:
Adam Dunkels 2014-03-24 08:42:28 +01:00
parent d1d82eb179
commit d53ba5aa3c
2 changed files with 633 additions and 0 deletions

365
core/net/ip/tcp-socket.c Normal file
View file

@ -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 <stdio.h>
#include <string.h>
#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;
}
/*---------------------------------------------------------------------------*/

268
core/net/ip/tcp-socket.h Normal file
View file

@ -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 */