527 lines
13 KiB
C
527 lines
13 KiB
C
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <netinet/in.h>
|
|
#include <netdb.h>
|
|
#include <arpa/inet.h>
|
|
#include <assert.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <stdio.h>
|
|
#include "sics.h"
|
|
#include "splitter.h"
|
|
#include "ascon.i"
|
|
|
|
/*
|
|
CreateSocketAdress stolen from Tcl. Thanks to John Ousterhout
|
|
*/
|
|
|
|
static int CreateSocketAdress(
|
|
struct sockaddr_in *sockaddrPtr, /* Socket address */
|
|
char *host, /* Host. NULL implies INADDR_ANY */
|
|
int port) /* Port number */
|
|
{
|
|
struct hostent *hostent; /* Host database entry */
|
|
struct in_addr addr; /* For 64/32 bit madness */
|
|
|
|
(void) memset((char *) sockaddrPtr, '\0', sizeof(struct sockaddr_in));
|
|
sockaddrPtr->sin_family = AF_INET;
|
|
sockaddrPtr->sin_port = htons((unsigned short) (port & 0xFFFF));
|
|
if (host == NULL) {
|
|
addr.s_addr = INADDR_ANY;
|
|
} else {
|
|
hostent = gethostbyname(host);
|
|
if (hostent != NULL) {
|
|
memcpy((char *) &addr,
|
|
(char *) hostent->h_addr_list[0], (size_t) hostent->h_length);
|
|
} else {
|
|
addr.s_addr = inet_addr(host);
|
|
if (addr.s_addr == (unsigned long)-1) {
|
|
return 0; /* error */
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* There is a rumor that this assignment may require care on
|
|
* some 64 bit machines.
|
|
*/
|
|
sockaddrPtr->sin_addr.s_addr = addr.s_addr;
|
|
return 1;
|
|
}
|
|
|
|
double DoubleTime(void) {
|
|
struct timeval now;
|
|
/* the resolution of this function is usec, if the machine supports this
|
|
and the mantissa of a double is 51 bits or more (31 for sec and 20 for micro)
|
|
*/
|
|
gettimeofday(&now, NULL);
|
|
return now.tv_sec + now.tv_usec / 1e6;
|
|
}
|
|
|
|
void AsconError(Ascon *a, char *msg, int errorno) {
|
|
static char *stateText[]={
|
|
"state 0", "kill", "state 2", "notConnected",
|
|
"connect", "start connect", "connect finished", "connect failed",
|
|
"write", "start write", "write finished", "write failed",
|
|
"read", "start read", "read finished", "read failed",
|
|
"state 16", "state 17", "state 18", "idle"
|
|
};
|
|
char *state;
|
|
|
|
if (a->state < 0 || a->state > 19) {
|
|
state = "bad state";
|
|
} else {
|
|
state = stateText[a->state];
|
|
}
|
|
if (errorno != 0) {
|
|
a->errList = ErrPutMsg(a->errList, &a->curError, "ASCERR: %s %s (during %s)", msg, strerror(errorno), state);
|
|
} else {
|
|
a->errList = ErrPutMsg(a->errList, &a->curError, "ASCERR: %s (during %s)", msg, state);
|
|
}
|
|
a->state |= AsconFailed;
|
|
}
|
|
|
|
static void AsconConnect(Ascon *a) {
|
|
/* input state: AsconConnectStart
|
|
output state: AsconFailed or AsconConnecting */
|
|
int ret;
|
|
struct sockaddr_in adr;
|
|
char *colon;
|
|
int port;
|
|
int oldopts;
|
|
|
|
if (a->fd < 0) {
|
|
a->fd = socket(AF_INET,SOCK_STREAM,0);
|
|
if (a->fd < 0) {
|
|
AsconError(a, "socket failed:", errno);
|
|
return;
|
|
}
|
|
}
|
|
colon = strchr(a->hostport, ':');
|
|
if (colon == NULL) return;
|
|
port = atoi(colon+1);
|
|
if (port <= 0) {
|
|
AsconError(a, "bad port number", 0);
|
|
return;
|
|
}
|
|
*colon = '\0';
|
|
ret = CreateSocketAdress(&adr, a->hostport, port);
|
|
*colon = ':';
|
|
if (ret == 0) {
|
|
AsconError(a, "bad host specification", 0);
|
|
return;
|
|
}
|
|
/* should we insert the workaround for lantronix server ? see network.c */
|
|
oldopts = fcntl(a->fd, F_GETFL, 0);
|
|
fcntl(a->fd, F_SETFL, oldopts | O_NONBLOCK);
|
|
ret = connect(a->fd, (struct sockaddr *)&adr, sizeof(struct sockaddr_in));
|
|
if (ret < 0) {
|
|
if (errno != EINPROGRESS) {
|
|
AsconError(a, "connect failed:", errno);
|
|
return;
|
|
}
|
|
}
|
|
a->state = AsconConnecting;
|
|
return;
|
|
}
|
|
|
|
int AsconStdInit(Ascon *a, SConnection *con,
|
|
int argc, char *argv[]) {
|
|
a->fd = -1;
|
|
a->state = AsconConnectStart;
|
|
a->reconnectInterval = 10;
|
|
a->hostport = strdup(argv[1]);
|
|
a->hostport = strdup(argv[1]);
|
|
if(argc < 2){
|
|
a->sendTerminator = strdup(argv[2]);
|
|
} else {
|
|
a->sendTerminator = strdup("\n");
|
|
}
|
|
if(argc > 3){
|
|
a->timeout = atof(argv[3]);
|
|
} else {
|
|
a->timeout = 2.0; /* sec */
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int AsconReadGarbage(int fd) {
|
|
fd_set rmask;
|
|
struct timeval tmo = {0,0};
|
|
int l, ret, result;
|
|
char garbage[100];
|
|
|
|
FD_ZERO(&rmask);
|
|
result = 0;
|
|
do {
|
|
FD_SET(fd, &rmask);
|
|
ret = select(fd + 1, &rmask, NULL, NULL, &tmo);
|
|
if (ret > 0) {
|
|
l = recv(fd, garbage, sizeof garbage, 0);
|
|
if (l > 0) {
|
|
/* swallow */
|
|
result += l;
|
|
} else if (l == 0) {
|
|
errno = ECONNRESET;
|
|
return -2;
|
|
} else if (l < 0) {
|
|
return -2;
|
|
}
|
|
}
|
|
} while (ret > 0);
|
|
return result;
|
|
}
|
|
|
|
void PrintChar(char chr) {
|
|
if (chr <= 32 || chr >= 127) {
|
|
printf("%2.2x ", chr);
|
|
} else {
|
|
printf(" %c ", chr);
|
|
}
|
|
}
|
|
|
|
int AsconConnectSuccess(int fd) {
|
|
fd_set wmask, rmask;
|
|
struct timeval tmo = {0,0};
|
|
int oldopts;
|
|
int ret;
|
|
|
|
oldopts = fcntl(fd, F_GETFL, 0);
|
|
assert(oldopts | O_NONBLOCK); /* fd must be in non-blocking mode */
|
|
|
|
FD_ZERO(&wmask);
|
|
FD_ZERO(&rmask);
|
|
FD_SET(fd, &wmask);
|
|
FD_SET(fd, &rmask);
|
|
ret = select(fd + 1, &rmask, &wmask, NULL, &tmo);
|
|
if (ret > 0) {
|
|
assert(FD_ISSET(fd, &wmask));
|
|
if (FD_ISSET(fd, &rmask)) { /* there may already be data for read */
|
|
if (recv(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */
|
|
ret = ASCON_RECV_ERROR; /* first recv failed */
|
|
}
|
|
} else {
|
|
if (send(fd, NULL, 0, 0) < 0) { /* zero length, check only return value */
|
|
ret = ASCON_SEND_ERROR; /* first send failed */
|
|
}
|
|
}
|
|
}
|
|
fcntl(fd, F_SETFL, oldopts & ~ O_NONBLOCK); /* reset to blocking mode */
|
|
return ret;
|
|
}
|
|
|
|
int AsconReadChar(int fd, char *chr) {
|
|
fd_set rmask;
|
|
struct timeval tmo = {0,0};
|
|
int ret;
|
|
|
|
FD_ZERO(&rmask);
|
|
FD_SET(fd, &rmask);
|
|
ret = select(fd + 1, &rmask, NULL, NULL, &tmo);
|
|
if (ret <= 0) return ret;
|
|
ret = recv(fd, chr, 1, 0);
|
|
/* PrintChar(*chr); */
|
|
fflush(stdout);
|
|
if (ret > 0) return 1;
|
|
if (ret == 0) {
|
|
errno = ECONNRESET;
|
|
return ASCON_DISCONNECTED;
|
|
}
|
|
return ASCON_RECV_ERROR;
|
|
}
|
|
|
|
int AsconWriteChars(int fd, char *data, int length) {
|
|
fd_set wmask;
|
|
struct timeval tmo = {0,0};
|
|
int ret;
|
|
|
|
if (length <= 0) return 0;
|
|
/*
|
|
{ int i;
|
|
for (i=0; i<length; i++) {
|
|
PrintChar(data[i]);
|
|
}
|
|
}
|
|
printf("<written\n");
|
|
*/
|
|
FD_ZERO(&wmask);
|
|
FD_SET(fd, &wmask);
|
|
ret = select(fd + 1, NULL, &wmask, NULL, &tmo);
|
|
if (ret <= 0) return ASCON_SELECT_ERROR;
|
|
ret = send(fd, data, length, 0);
|
|
if (ret > 0) return ret;
|
|
if (ret == 0) {
|
|
errno = ECONNRESET;
|
|
return ASCON_DISCONNECTED;
|
|
}
|
|
return ASCON_SEND_ERROR;
|
|
}
|
|
|
|
static double lastCall = 0;
|
|
|
|
int AsconStdHandler(Ascon *a) {
|
|
int ret;
|
|
int l;
|
|
char chr;
|
|
double now = DoubleTime();
|
|
|
|
if (now > lastCall + 0.5) { /* AsconStdHandler was not called since a long time (0.5 sec) */
|
|
if (lastCall != 0) { /* extend timeout time (for debugging purposes) */
|
|
a->start += now - lastCall - 0.5;
|
|
}
|
|
}
|
|
lastCall = now;
|
|
switch (a->state) {
|
|
case AsconKillMe: return 0;
|
|
case AsconConnectStart:
|
|
AsconConnect(a);
|
|
break;
|
|
case AsconConnecting:
|
|
ret = AsconConnectSuccess(a->fd);
|
|
if (ret == 0) {
|
|
/* in progress */
|
|
} else if (ret > 0) {
|
|
a->state = AsconConnectDone; /* success */
|
|
} else if (ret < 0) {
|
|
AsconError(a, "AsconConnectSuccess failed:", errno);
|
|
}
|
|
break;
|
|
case AsconWriteStart:
|
|
DynStringConcat(a->wrBuffer, a->sendTerminator);
|
|
a->wrPos = 0;
|
|
a->state = AsconWriting;
|
|
break;
|
|
case AsconWriting:
|
|
AsconReadGarbage(a->fd);
|
|
l = GetDynStringLength(a->wrBuffer) - a->wrPos;
|
|
ret = AsconWriteChars(a->fd, GetCharArray(a->wrBuffer) + a->wrPos, l);
|
|
if (ret < 0) {
|
|
AsconError(a, "send failed:", errno);
|
|
} else {
|
|
a->wrPos += ret;
|
|
if (a->wrPos >= GetDynStringLength(a->wrBuffer)) {
|
|
a->state = AsconWriteDone;
|
|
}
|
|
}
|
|
break;
|
|
case AsconReadStart:
|
|
DynStringClear(a->rdBuffer);
|
|
a->start = DoubleTime();
|
|
a->state = AsconReading;
|
|
break;
|
|
case AsconReading:
|
|
ret = AsconReadChar(a->fd, &chr);
|
|
while (ret > 0) {
|
|
a->start = DoubleTime();
|
|
|
|
if (chr == '\n') {
|
|
if (a->readState) {
|
|
/* swallow LF after CR */
|
|
DynStringClear(a->rdBuffer);
|
|
a->readState = 0;
|
|
} else {
|
|
DynStringConcatChar(a->rdBuffer, '\0');
|
|
a->state = AsconReadDone;
|
|
break;
|
|
}
|
|
} else if (chr == '\r') {
|
|
a->readState = 1;
|
|
DynStringConcatChar(a->rdBuffer, '\0');
|
|
a->state = AsconReadDone;
|
|
break;
|
|
} else {
|
|
if (DynStringConcatChar(a->rdBuffer, chr) == 0) {
|
|
AsconError(a, "DynStringConcatChar failed:", ENOMEM);
|
|
break;
|
|
}
|
|
a->readState = 0;
|
|
}
|
|
ret = AsconReadChar(a->fd, &chr);
|
|
}
|
|
if (ret < 0) {
|
|
AsconError(a, "AsconReadChar failed:", errno);
|
|
return 1;
|
|
}
|
|
if (a->state == AsconReadDone) {
|
|
DynStringConcatChar(a->rdBuffer, '\0');
|
|
} else {
|
|
if (a->timeout > 0) {
|
|
if (DoubleTime() - a->start > a->timeout) {
|
|
AsconError(a, "read timeout", 0);
|
|
a->state = AsconTimeout;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
return 1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
/* define type AsconProtocolList and functions AsconProtocolAdd etc. */
|
|
#define MC_NAME(T) AsconProtocol##T
|
|
#include "mclist.c"
|
|
|
|
static AsconProtocolList protocols={0};
|
|
|
|
void AsconInsertProtocol(AsconProtocol *protocol) {
|
|
AsconProtocolAdd(&protocols, protocol);
|
|
}
|
|
|
|
AsconHandler AsconSetHandler(Ascon *a, SConnection *con,
|
|
int argc, char *argv[]) {
|
|
AsconProtocol *p;
|
|
|
|
if (argc < 1) return NULL;
|
|
if (strcasecmp(argv[0], "std") == 0) {
|
|
if (argc != 2) return NULL;
|
|
AsconStdInit(a, con, argc, argv);
|
|
return AsconStdHandler;
|
|
}
|
|
for (p = protocols.head; p!= NULL; p=p->next) {
|
|
if (strcasecmp(p->name, argv[0]) == 0) {
|
|
if(p->init(a, con, argc, argv)){
|
|
return p->handler;
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* --- implementation of higher level interface ---- */
|
|
|
|
char *ConcatArgs(int argc, char *argv[]) {
|
|
return Arg2Tcl(argc, argv, NULL, -1);
|
|
}
|
|
|
|
Ascon *AsconMake(SConnection *con, int argc, char *argv[]) {
|
|
Ascon *a;
|
|
char *args;
|
|
|
|
a = calloc(1, sizeof(*a));
|
|
if (a == NULL) {
|
|
SCWrite(con, "ERROR: no memory", eError);
|
|
return NULL;
|
|
}
|
|
a->handler = AsconSetHandler(a, con, argc, argv);
|
|
if (a->handler == NULL) {
|
|
args = ConcatArgs(argc, argv);
|
|
if (!args) return NULL;
|
|
SCPrintf(con, eError, "ERROR: illegal protocol: %s", args);
|
|
free(args);
|
|
return NULL;
|
|
}
|
|
a->rdBuffer = CreateDynString(60, 63);
|
|
a->wrBuffer = CreateDynString(60, 63);
|
|
a->errList = NULL;
|
|
a->responseValid = 0;
|
|
a->reconnectInterval = 10;
|
|
a->lastReconnect = 0;
|
|
return a;
|
|
}
|
|
|
|
void AsconKill(Ascon *a) {
|
|
a->state = AsconKillMe;
|
|
a->handler(a);
|
|
if (a->fd > 0) {
|
|
close(a->fd);
|
|
}
|
|
DeleteDynString(a->rdBuffer);
|
|
DeleteDynString(a->wrBuffer);
|
|
if (a->hostport) {
|
|
free(a->hostport);
|
|
}
|
|
if(a->sendTerminator){
|
|
free(a->sendTerminator);
|
|
}
|
|
if(a->private != NULL && a->killPrivate != NULL){
|
|
a->killPrivate(a->private);
|
|
}
|
|
free(a);
|
|
}
|
|
|
|
AsconStatus AsconTask(Ascon *a) {
|
|
double now;
|
|
|
|
while (a->handler(a)) {
|
|
switch (a->state) {
|
|
case AsconReading:
|
|
case AsconWriting:
|
|
return AsconPending;
|
|
case AsconNotConnected:
|
|
return AsconOffline;
|
|
break;
|
|
case AsconConnectDone:
|
|
a->state = AsconIdle;
|
|
return AsconReady;
|
|
case AsconWriteDone:
|
|
if (a->noResponse) {
|
|
return AsconReady;
|
|
}
|
|
a->state = AsconReadStart;
|
|
break;
|
|
case AsconReadDone:
|
|
a->state = AsconIdle;
|
|
a->responseValid = 1;
|
|
return AsconReady;
|
|
case AsconConnecting:
|
|
return AsconUnconnected;
|
|
default:
|
|
switch (a->state % 4) {
|
|
case AsconOnTheWay:
|
|
case AsconStart:
|
|
return AsconPending;
|
|
case AsconFailed:
|
|
if (a->state != AsconTimeout) {
|
|
now = DoubleTime();
|
|
if (now > a->lastReconnect + a->reconnectInterval) {
|
|
a->lastReconnect = now;
|
|
a->state = AsconConnectStart;
|
|
}
|
|
}
|
|
return AsconFailure;
|
|
case AsconFinished:
|
|
if (a->state < AsconConnectFailed) {
|
|
return AsconUnconnected;
|
|
}
|
|
return AsconReady;
|
|
}
|
|
}
|
|
}
|
|
return AsconIdle;
|
|
}
|
|
|
|
int AsconWrite(Ascon *a, char *command, int noResponse) {
|
|
if (a->state <= AsconConnectFailed || a->state % 4 < AsconFinished) return 0;
|
|
DynStringCopy(a->wrBuffer, command);
|
|
a->noResponse = noResponse;
|
|
a->state = AsconWriteStart;
|
|
a->responseValid = 0;
|
|
AsconTask(a);
|
|
return 1;
|
|
}
|
|
|
|
char *AsconRead(Ascon *a) {
|
|
if (a->noResponse) {
|
|
a->noResponse=0;
|
|
return "";
|
|
}
|
|
if (a->state % 4 == AsconFailed) {
|
|
a->state = AsconIdle;
|
|
return "";
|
|
}
|
|
if (a->responseValid) {
|
|
a->responseValid = 0;
|
|
return GetCharArray(a->rdBuffer);
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
ErrMsg *AsconGetErrList(Ascon *a) {
|
|
return a->curError;
|
|
}
|