Files
sics/site_ansto/hardsup/Monitor/sock.c
2013-12-17 16:53:28 +11:00

525 lines
11 KiB
C

#include "sock.h"
#include "utility.h"
#include "display.h"
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/poll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */
#define LINE_LEN 1024
#define MAX_SOCK 200
/**
* Mode of the socket
*/
typedef enum terminal_mode_t
{
/** unknown or uninitialised */
term_idle = 0,
/** telnet socket */
term_tty,
/** web page GET */
term_page,
/** web form POST */
term_form,
/** SOAP request */
term_soap
} TERM_MODE;
/**
* Terminal Control Structure
*
* Maintains the state of the connection
*/
typedef struct terminal_t
{
/** file descriptor for the socket */
int fd;
/** mode of the connection */
TERM_MODE mode;
/** current state of the connection */
int state;
/** value to match for reports */
int match;
/** TOD socket connected */
struct timeval connect_time;
/** address of peer */
struct sockaddr_in addr;
/** function to handle input ready */
void (*input)(int idx);
/** input line buffer */
char line[LINE_LEN];
/** length of text in line */
int line_len;
/** URL for GET/POST */
char url[LINE_LEN];
/** value from Content-Length header */
int content_length;
/** associated device */
int(*command)(void* device, const char* cmd);
void* device;
} TERMINAL, *pTERMINAL;
/**
* Array of terminal control structures
*/
static TERMINAL fdv[MAX_SOCK];
/**
* This structure parallels the terminal control structure
*/
static struct pollfd fds[MAX_SOCK];
/** Number of active connections */
static int num_fds = 0;
/** descriptor of the listen socket */
static int sock_l;
/**
* Initialise the socket interface
*
*/
void sock_init(void)
{
int i;
dbg_printf(0, "sock_init\n");
memset(fdv, 0, sizeof(fdv));
for (i = 0; i < MAX_SOCK; ++i)
{
fdv[i].fd = -1;
}
memset(fds, 0, sizeof(fds));
num_fds = 0;
}
/**
*
* Opens and binds the listen socket and listens for incomming
* connections.
*
* \param addr the TCP/IP port number on which to listen
* \param command function to process device commands from the socket
* \param device context argument to device command
*/
void sock_listen(int addr, int (*command)(void* device, const char* cmd), void* device)
{
int status;
long flags;
int one = 1;
struct sockaddr_in my_addr;
status = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
if (status < 0)
{
perror("socket");
exit(EXIT_FAILURE);
}
sock_l = status;
flags = fcntl(sock_l, F_GETFL);
flags |= O_NONBLOCK;
fcntl(sock_l, F_SETFL, flags);
setsockopt(sock_l, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
memset(&my_addr, 0, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(addr);
my_addr.sin_addr.s_addr = INADDR_ANY;
status = bind(sock_l, (struct sockaddr*) &my_addr, sizeof(my_addr));
if (status < 0)
{
perror("bind");
exit(EXIT_FAILURE);
}
status = listen(sock_l, 5);
if (status < 0)
{
perror("listen");
exit(EXIT_FAILURE);
}
fds[num_fds].fd = sock_l;
fds[num_fds].events = POLLIN | POLLOUT;
fdv[num_fds].fd = sock_l;
fdv[num_fds].input = sock_accept;
fdv[num_fds].command = command;
fdv[num_fds].device = device;
++num_fds;
}
/**
* Check for socket activity
*
* Polls active sockets for activity: connections on the listen socket or
* received data on the active sockets.
*
* \param timeout wait time in milliseconds
*/
void sock_check(int timeout)
{
int i;
int ready;
ready = poll(fds, num_fds, timeout);
if (ready < 0)
{
perror("poll");
return;
}
if (ready == 0)
return;
dbg_printf(0, "sock_check, ready=%d\n", ready);
for (i = 0; i < MAX_SOCK; ++i)
{
if (fds[i].revents)
{
if (fds[i].revents & POLLIN)
fdv[i].input(i);
if (--ready <= 0)
break;
}
}
}
/**
* Close and remove an active or disconnected socket
*
* \param n index of socket
*/
void sock_close(int n)
{
dbg_printf(0, "sock_close\n");
shutdown(fdv[n].fd, SHUT_RDWR);
close(fdv[n].fd);
--num_fds;
if (n != num_fds)
{
fdv[n] = fdv[num_fds];
fds[n] = fds[num_fds];
}
fdv[num_fds].fd = -1;
}
/**
* Handle a line of input from a socket
*
* \param n index of socket
*/
void sock_line(int n)
{
char *cp = &fdv[n].line[0];
char *up;
dbg_printf(0, "%3d: %s", fdv[n].state, fdv[n].line);
if (fdv[n].line[fdv[n].line_len - 1] != '\n')
dbg_printf(0, "\n");
switch(fdv[n].state)
{
case 0:
/*
* we are looking for HTTP GET or POST or a SICS command
*/
if (toupper(cp[0]) == 'G' &&
toupper(cp[1]) == 'E' &&
toupper(cp[2]) == 'T' &&
isspace(cp[3]) &&
strstr(cp, "HTTP/"))
{
cp+= 4;
while (isspace(*cp))
++cp;
up = &fdv[n].url[0];
while (*cp && !isspace(*cp))
*up++ = *cp++;
*up = '\0';
fdv[n].state = 1;
}
else if (toupper(cp[0]) == 'P' &&
toupper(cp[1]) == 'O' &&
toupper(cp[2]) == 'S' &&
toupper(cp[3]) == 'T' &&
isspace(cp[4]) &&
strstr(cp, "HTTP/"))
{
cp+= 4;
while (isspace(*cp))
++cp;
up = &fdv[n].url[0];
while (*cp && !isspace(*cp))
*up++ = *cp++;
*up = '\0';
fdv[n].content_length = 0;
fdv[n].state = 3;
}
else if (toupper(cp[0]) == 'S' &&
toupper(cp[1]) == 'I' &&
toupper(cp[2]) == 'C' &&
toupper(cp[3]) == 'S' &&
isspace(cp[4]))
{
BUFFER buf;
memcpy(buf.body, fdv[n].line, fdv[n].line_len);
buf.length = fdv[n].line_len;
buf.body[buf.length] = '\0';
process_command(n, &buf);
fdv[n].content_length = 0;
fdv[n].state = 0;
}
else
{
BUFFER buf;
memcpy(buf.body, fdv[n].line, fdv[n].line_len);
buf.length = fdv[n].line_len;
buf.body[buf.length] = '\0';
process_command(n, &buf);
fdv[n].content_length = 0;
fdv[n].state = 0;
}
break;
case 1:
/*
* We are scanning the header of a GET
*/
while (*cp == '\r' || *cp == '\n')
++cp;
if (!*cp)
{
if (strncasecmp(fdv[n].url, "/form", 5) == 0)
put_form(n);
else if (strncasecmp(fdv[n].url, "/cmd=", 5) == 0)
{
fdv[n].command(fdv[n].device, &fdv[n].url[5]);
put_page_refresh(n);
}
else
put_page(n);
fdv[n].state = 0;
}
break;
case 3:
/*
* we are scanning the header of a POST
*/
while (*cp == '\r' || *cp == '\n')
++cp;
if (!*cp)
{
if (fdv[n].content_length == 0)
fdv[n].state = 5;
else
fdv[n].state = 4;
break;
}
if (strncasecmp("Content-Length", cp, 14) == 0 &&
(cp = strchr(&cp[14], ':')))
{
fdv[n].content_length = atoi(&cp[1]);
dbg_printf(0, "Content Length = %d\n", fdv[n].content_length);
}
break;
case 4:
/*
* we are scanning the body of a POST
*/
dbg_printf(0, "Content-Length: %d, Line-Length: %d\n",
fdv[n].content_length, fdv[n].line_len);
fdv[n].content_length -= fdv[n].line_len;
if (fdv[n].content_length <= 0)
{
BUFFER buf;
memcpy(buf.body, fdv[n].line, fdv[n].line_len);
buf.length = fdv[n].line_len;
process_form(n, &buf);
fdv[n].state = 5;
}
break;
default:
break;
}
/*
* If we have reached the end of a POST, send the new form
*/
if (fdv[n].state == 5)
{
put_form_refresh(n);
fdv[n].state = 0;
}
}
/**
* Handle data received on a socket
*
* \param n index of socket
*/
void sock_input(int n)
{
dbg_printf(0, "sock_input(%d)\n", n);
ssize_t sz;
char buffer[1024];
sz = recv(fdv[n].fd, &buffer, sizeof(buffer), 0);
if (sz == 0)
{
sock_close(n);
return;
}
if (sz < 0)
{
switch (errno)
{
case EAGAIN: /* AKA EWOULDBLOCK */
dbg_printf(0, "EAGAIN:");
break;
case EINTR:
dbg_printf(0, "EINTR:");
break;
default:
perror("recv");
sock_close(n);
}
return;
}
int i;
for (i = 0; i < sz; ++i)
{
if (fdv[n].line_len < LINE_LEN - 1)
fdv[n].line[fdv[n].line_len++] = buffer[i];
if (buffer[i] == '\n' ||
(fdv[n].state == 4 &&
fdv[n].line_len == fdv[n].content_length)) {
fdv[n].line[fdv[n].line_len] = '\0';
sock_line(n);
fdv[n].line_len = 0;
}
}
return;
}
/**
* Handle connection arrived on listen socket
*
* \param n index of socket
*/
void sock_accept(int n)
{
dbg_printf(0, "sock_accept(%d)\n", n);
int sock_n;
struct sockaddr_in my_addr;
socklen_t my_len = sizeof(my_addr);
sock_n = accept(fdv[n].fd, (struct sockaddr*) &my_addr, &my_len);
if (sock_n < 0)
{
perror("accept");
return;
}
if (num_fds < MAX_SOCK)
{
fdv[num_fds].fd = sock_n;
fdv[num_fds].addr = my_addr;
fdv[num_fds].input = sock_input;
fdv[num_fds].command = fdv[n].command;
fdv[num_fds].device = fdv[n].device;
fdv[num_fds].line_len = 0;
fdv[num_fds].state = 0;
fdv[num_fds].match = 0;
fds[num_fds].fd = sock_n;
fds[num_fds].events = POLLIN;
++num_fds;
}
else
{
/* maximum connections active */
/* TODO log error */
close(sock_n);
}
}
/**
* Send data to active connection
*
* The data in the buffer is sent to the socket
*
* \param n index of socket
* \param bp pointer to buffer to send
*/
void sock_send(int n, BUFFER* bp)
{
int status;
status = send(fdv[n].fd, bp->body, bp->length, MSG_NOSIGNAL);
if (status < 0)
{
if (errno == EAGAIN) /* flooded */
;
else if (errno == EPIPE)
{
perror("send EPIPE");
sock_close(n);
}
else
{
perror("send");
sock_close(n);
}
}
}
/**
* The report in the buffer is sent to all active sockets which match
*
* \param bp pointer to buffer containing report
* \param match value to match for this report
* \param dev device context to match for this report
*/
void sock_report(BUFFER* bp, int match, void* dev)
{
int i;
for (i = 1; i < num_fds; ++i)
{
if (fdv[i].match == match && fdv[i].device == dev)
sock_send(i, bp);
}
}
/**
* Set the match parameter for the socket
*
* \param n index of socket
* \param match value to match for this socket
*/
void sock_set_match(int n, int match)
{
fdv[n].match = match;
}
/**
* Send an OK to the socket
*
* \param n index of socket
*/
void sock_ok(int n)
{
BUFFER buffer;
strcpy(buffer.body, "OK\r\n");
buffer.length = strlen(buffer.body);
sock_send(n, &buffer);
}
/**
* Send an ERR to the socket
*
* \param n index of socket
*/
void sock_err(int n)
{
BUFFER buffer;
strcpy(buffer.body, "ERR\r\n");
buffer.length = strlen(buffer.body);
sock_send(n, &buffer);
}
void* sock_device(int n)
{
return fdv[n].device;
}