- introduce script context
This commit is contained in:
493
ascon.c
Normal file
493
ascon.c
Normal file
@ -0,0 +1,493 @@
|
||||
#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;
|
||||
|
||||
gettimeofday(&now, NULL);
|
||||
return now.tv_sec + now.tv_usec / 1e6;
|
||||
}
|
||||
|
||||
static void AsconError(AsconPtr 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) {
|
||||
ErrPutMsg(&a->errList, NULL, "%s %s (during %s)", msg, strerror(errorno), state);
|
||||
} else {
|
||||
ErrPutMsg(&a->errList, NULL, "%s (during %s)", msg, state);
|
||||
}
|
||||
a->state |= AsconFailed;
|
||||
}
|
||||
|
||||
static void AsconConnect(AsconPtr 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;
|
||||
}
|
||||
|
||||
void AsconStdInit(AsconPtr a, char *hostport) {
|
||||
a->fd = -1;
|
||||
a->state = AsconConnectStart;
|
||||
a->timeout = 2.0; /* sec */
|
||||
a->hostport = strdup(hostport);
|
||||
}
|
||||
|
||||
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(AsconPtr 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:
|
||||
DynStringConcatChar(a->wrBuffer, '\r');
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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(AsconPtr a, int argc, char *argv[]) {
|
||||
AsconProtocol *p;
|
||||
|
||||
if (argc < 1) return NULL;
|
||||
if (strcasecmp(argv[0], "std") == 0) {
|
||||
if (argc != 2) return NULL;
|
||||
AsconStdInit(a, argv[1]);
|
||||
return AsconStdHandler;
|
||||
}
|
||||
for (p = protocols.head; p!= NULL; p=p->next) {
|
||||
if (strcasecmp(p->name, argv[0]) == 0) {
|
||||
p->init(a, argc, argv);
|
||||
return p->handler;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* --- implementation of higher level interface ---- */
|
||||
|
||||
char *ConcatArgs(int argc, char *argv[]) {
|
||||
return Arg2Tcl(argc, argv, NULL, -1);
|
||||
}
|
||||
|
||||
AsconPtr AsconMake(SConnection *con, int argc, char *argv[]) {
|
||||
AsconPtr a;
|
||||
char *args;
|
||||
|
||||
a = calloc(1, sizeof(*a));
|
||||
if (a == NULL) {
|
||||
SCWrite(con, "ERROR: no memory", eError);
|
||||
return NULL;
|
||||
}
|
||||
a->handler = AsconSetHandler(a, 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.head = NULL;
|
||||
a->responseValid = 0;
|
||||
return a;
|
||||
}
|
||||
|
||||
void AsconKill(AsconPtr 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);
|
||||
}
|
||||
free(a);
|
||||
}
|
||||
|
||||
AsconState AsconTask(AsconPtr a) {
|
||||
a->handler(a);
|
||||
while (1) {
|
||||
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 <= AsconConnectFailed) {
|
||||
return AsconUnconnected;
|
||||
}
|
||||
return AsconFailure;
|
||||
case AsconFinished:
|
||||
if (a->state < AsconConnectFailed) {
|
||||
return AsconUnconnected;
|
||||
}
|
||||
return AsconReady;
|
||||
}
|
||||
}
|
||||
a->handler(a);
|
||||
}
|
||||
}
|
||||
|
||||
int AsconWrite(AsconPtr 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(AsconPtr 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;
|
||||
}
|
||||
|
||||
ErrMsgList *AsconGetErrList(AsconPtr a) {
|
||||
return &a->errList;
|
||||
}
|
Reference in New Issue
Block a user