#include #include #include #include #include #include #include #include #include "sics.h" #include "splitter.h" #include "ascon.i" #include "uselect.h" #include "socketaddr.h" static double lastClose = 0; /* time of last close operation */ static AsconProtocol *protocols = NULL; void AsconError(Ascon *a, char *msg, int errorno) { static char *stateText[]={ "not connected", "connect start", "connecting", "connect done", "write start", "writing", "write done", "read start", "reading", "read done", "idle", "failed", "no response" }; char *state; char num[8]; if ( a->state >= AsconMaxState) { state = "bad state"; } else { state = stateText[a->state]; } DynStringCopy(a->errmsg, "ASCERR: "); if (errorno == 0) { DynStringConcat(a->errmsg, msg); DynStringConcat(a->errmsg, " ("); DynStringConcat(a->errmsg, state); DynStringConcat(a->errmsg, " state)"); DynStringConcat(a->errmsg, " on "); DynStringConcat(a->errmsg, a->hostport); } else { DynStringConcat(a->errmsg, strerror(errorno)); DynStringConcat(a->errmsg, " ("); DynStringConcat(a->errmsg, state); DynStringConcat(a->errmsg, " state, "); DynStringConcat(a->errmsg, msg); DynStringConcat(a->errmsg, ")"); DynStringConcat(a->errmsg, " on "); DynStringConcat(a->errmsg, a->hostport); } a->state = AsconFailed; } static void AsconConnect(Ascon * a) { /* input state: AsconConnectStart output state: AsconFailed or AsconConnecting */ int ret; struct sockaddr adr; char *colon; int port; int oldopts; char ipaddress[16]; /* wait 0.5 sec before connecting again after a close 2 reasons for that: - it seems that connecting immediately to a closed port fails. We will avoid some "Connection refused" error messages. - a bug in the lantronix terminal: if a channel is closed and reopened within short time the connect may be succesful, but a message will be sent on the channel! In principle we need only to wait when connecting to the same address and port, but bookkeeping would be too complicated. */ if (DoubleTime() < lastClose + 0.5) return; if (a->fd < 0) { a->fd = socket(AF_INET, SOCK_STREAM, 0); if (a->fd < 0) { AsconError(a, "ASC1", errno); return; } } if (a->hostport == NULL) { AsconError(a, "no host:port given", 0); return; } colon = strchr(a->hostport, ':'); if (colon == NULL) { if (strcmp(a->hostport, "unconnected") == 0) { AsconError(a, "disconnected", 0); } else { AsconError(a, "expected 'host:port' or 'unconnected'", 0); } return; } port = atoi(colon + 1); if (port <= 0) { AsconError(a, "bad port number", 0); return; } *colon = '\0'; ret = MakeSocketAddr(&adr, a->hostport, port, a->ip); *colon = ':'; if (ret == 0) { AsconError(a, "bad host specification", 0); return; } oldopts = fcntl(a->fd, F_GETFL, 0); fcntl(a->fd, F_SETFL, oldopts | O_NONBLOCK); ret = connect(a->fd, &adr, sizeof(struct sockaddr_in)); if (ret < 0) { switch (errno) { case EINPROGRESS: case EALREADY: case EISCONN: a->state = AsconConnecting; break; default: AsconError(a, "ASC2", errno); return; } } a->state = AsconConnecting; return; } 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 = uselect(fd + 1, &rmask, NULL, NULL, &tmo); if (ret > 0) { l = recv(fd, garbage, sizeof garbage - 1, 0); if (l > 0) { /* swallow */ garbage[l] = '\0'; /* printf("(((%s)))\n", garbage); */ 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 = uselect(fd + 1, &rmask, &wmask, NULL, &tmo); if (ret > 0) { /** * MK: * This assertion triggered for some reason: as writing is only done later * I moved the test for ISSET(wmask) down there * assert(FD_ISSET(fd, &wmask)); * MZ: * I remember having found this logic on the www. Anyway, your way must be o.k. too */ 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 (FD_ISSET(fd,&wmask)) { 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 = uselect(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 0) return ret; if (ret == 0) { errno = ECONNRESET; return ASCON_DISCONNECTED; } return ASCON_SEND_ERROR; } static double lastCall = 0; int AsconBaseHandler(Ascon * a) { int ret; int l; char chr; double now = DoubleTime(); if (now > lastCall + 0.5) { /* AsconBaseHandler 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 AsconConnectStart: AsconConnect(a); break; case AsconConnecting: ret = AsconConnectSuccess(a->fd); if (ret == 0) { /* in progress */ } else if (ret > 0) { DynStringClear(a->errmsg); a->state = AsconConnectDone; /* success */ } else if (ret < 0) { AsconError(a, "ASC3", errno); } break; case AsconWriteStart: if (a->sendTerminator) { 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, "ASC4", errno); /* sets state to AsconFailed */ } 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); if (ret <= 0) { if (ret < 0) { AsconError(a, "ASC5", errno); return 0; } if (a->timeout > 0) { if (DoubleTime() - a->start > a->timeout) { AsconError(a, "no response", 0); a->state = AsconTimeout; } } return 0; } a->lastChar = chr; a->start = DoubleTime(); if (DynStringConcatChar(a->rdBuffer, chr) == 0) { AsconError(a, "ASC6", errno); return 0; } break; default: break; } return 1; } void AsconInsertProtocol(AsconProtocol *protocol) { AsconProtocol *p; for (p = protocols; p != NULL; p = p->next) { if (p == protocol) return; } protocol->next = protocols; 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; if (AsconStdInit(a, con, argc, argv)) { return AsconStdHandler; } else { return NULL; } } for (p = protocols; 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; } /* --- standard handler ---- */ int AsconStdHandler(Ascon * a) { int result; char chr; int ret, l; char *cmd, *opt, *buf; switch (a->state) { case AsconWriteStart: cmd = GetCharArray(a->wrBuffer); a->lineCount = 1; if (a->separator != NULL) { /* multiline mode enabled */ l = strlen(cmd); if (l> 0 && cmd[l-1] == '}') { opt = strrchr(cmd, '{'); if (opt != NULL) { if (sscanf(opt, "{%d}", &a->lineCount) == 1) { /* remove option */ for (l = strlen(opt); l > 0; l--) { DynStringBackspace(a->wrBuffer); } } } } } else if (strstr(GetCharArray(a->wrBuffer), "@@NOSEND@@") != NULL) { a->state = AsconWriteDone; return 1; } if(a->lineCount == 0){ a->noResponse = 1; } else { a->noResponse = 0; } break; /* go to the base handler */ case AsconReading: if (a->lineCount == 0) { /* no response expected */ a->state = AsconReadDone; return 1; } result = AsconBaseHandler(a); if (result == 0) return 0; chr = a->lastChar; if (chr == '\0') { /* skip NUL characters */ DynStringBackspace(a->rdBuffer); /* remove NUL */ return 1; } if (a->replyTerminator != NULL) { if (strchr(a->replyTerminator, chr) != NULL) { if (a->compositeTerminator) { /* one character was o.k., but all have to match */ l = strlen(a->replyTerminator); buf = GetCharArray(a->rdBuffer) + GetDynStringLength(a->rdBuffer) - l; if (strncmp(buf, a->replyTerminator, l) == 0) { a->state = AsconReadDone; } } else { a->state = AsconReadDone; if (chr == '\n' || chr == '\r') { DynStringBackspace(a->rdBuffer); /* remove LF or CR */ } } } } else { if (chr == '\n') { /* LF */ DynStringBackspace(a->rdBuffer); /* remove LF */ if (a->readState) { /* last char was CR */ /* LF after CR is not a terminator */ a->readState = 0; } else { a->state = AsconReadDone; } } else if (chr == '\r') { /* CR */ DynStringBackspace(a->rdBuffer); /* remove CR */ a->readState = 1; /* set 'last char was CR' */ a->state = AsconReadDone; } } if (a->state == AsconReadDone && a->lineCount > 1) { if (a->separator != NULL) { DynStringConcat(a->rdBuffer, a->separator); } a->lineCount--; a->state = AsconReading; } return 1; /* base handler was already called */ default: break; } return AsconBaseHandler(a); } static void checkTerminator(char *term) { int c, l; if (term == NULL) return; l = strlen(term); if (l > 1 && term[0] == '"' && term[l-1] == '"') { memmove(term, term+1, l-2); term[l-2] = 0; } if (strncmp(term,"0x",2) == 0) { sscanf(term,"%x",&c); term[0] = (char)c; term[1] = '\0'; } } /** * Treat hex strings as terminators right. Note that this * is limited to single character terminators. * M.Z. changed strstr to strncmp (more precise) * * M.Z. add new option (composite terminator): * convert 'term' to term */ void AsconCheckTerminators(Ascon *a) { int i, l; checkTerminator(a->sendTerminator); checkTerminator(a->replyTerminator); a->compositeTerminator = 0; if (a->replyTerminator != NULL && a->replyTerminator[0] == '\'') { l = strlen(a->replyTerminator); if (l > 2 && a->replyTerminator[l-1] == '\'') { for (i = 0; i < l - 2; i++) { a->replyTerminator[i] = a->replyTerminator[i+1]; } a->replyTerminator[l-2] = '\0'; a->compositeTerminator = 1; } } } int AsconInterpreteArgs(int argc, char *argv[], int parc, char *parn[], char *pars[]) { /* interprete arguments in the form "name=value" or with a fixed order. The result is 1 in case of success or 0 in case of an error. */ int ia, ip, l; for (ip = 0; ip < parc; ip++) { pars[ip] = NULL; } if (argc == 0) return 1; if (strchr(argv[0], '=') == NULL) { for (ia = 0; ia < argc; ia++) { if (ia >= ip) { return 0; } if (strchr(argv[ia], '=') != NULL) { return 0; } pars[ia] = argv[ia]; } return 1; } for (ia = 0; ia < argc; ia++) { for (ip = 0; ip < parc; ip++) { l = strlen(parn[ip]); if (strncasecmp(argv[ia], parn[ip], l) == 0 && argv[ia][l] == '=') { pars[ip] = argv[ia] + l + 1; break; } } if (ip >= parc) { return 0; } } return 1; } int AsconStdInit(Ascon *a, SConnection *con, int argc, char *argv[]) { enum nPars {NA=4}; char *pars[NA]; static char *parn[NA]={ "sendterminator", "timeout", "replyterminator", "lineseparator" }; char *msg; assert(argc>1); a->hostport = strdup(argv[1]); if (!AsconInterpreteArgs(argc-2, argv+2, NA, parn, pars)) { return 0; } if (pars[0]) { a->sendTerminator = strdup(pars[0]); } else { a->sendTerminator = strdup("\n"); } if (pars[1] && pars[1][0] != '\0') { a->timeout = atof(pars[1]); } else { a->timeout = 2.0; /* sec */ } if (pars[2] && pars[2][0] != '\0') { a->replyTerminator = strdup(pars[2]); } else { a->replyTerminator = NULL; } if (pars[3] && pars[3][0] != '\0') { a->separator = strdup(pars[3]); } else { a->separator = NULL; } AsconCheckTerminators(a); return 1; } /* --- 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->fd = -1; a->state = AsconConnectStart; a->timeout = 2.0; /* 2 sec default */ a->reconnectInterval = 10; /* 10 sec default */ a->replyTerminator = NULL; a->sendTerminator = NULL; a->hostport = NULL; a->responseValid = 0; a->readState = 0; a->lineCount = 1; a->separator = NULL; a->killPrivate = NULL; a->private = 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, 65536); a->wrBuffer = CreateDynString(60, 63); a->errmsg = CreateDynString(60, 63); return a; } void AsconKill(Ascon * a) { if (a->fd > 0) { close(a->fd); lastClose = DoubleTime(); } DeleteDynString(a->rdBuffer); DeleteDynString(a->wrBuffer); DeleteDynString(a->errmsg); if (a->hostport) { free(a->hostport); } if (a->sendTerminator) { free(a->sendTerminator); } if (a->replyTerminator) { free(a->replyTerminator); } if (a->separator) { free(a->separator); } if (a->private != NULL && a->killPrivate != NULL) { a->killPrivate(a->private); } free(a); } void AsconDisconnect(Ascon * a) { if (a->fd > 0) { close(a->fd); lastClose = DoubleTime(); } a->fd = -1; a->state = AsconNotConnected; } void AsconReconnect(Ascon * a, char * hostport) { if (a->fd > 0) { close(a->fd); lastClose = DoubleTime(); } if (hostport != NULL && hostport[0] != '\0') { if (a->hostport) { free(a->hostport); } a->hostport = strdup(hostport); } a->fd = -1; a->state = AsconConnectStart; } AsconStatus AsconTask(Ascon * a) { int result; double now; while (1) { result = a->handler(a); switch (a->state) { case AsconNotConnected: return AsconOffline; case AsconConnecting: return AsconConnectPending; case AsconConnectDone: a->state = AsconIdle; DynStringClear(a->errmsg); /* connection o.k. */ return AsconReady; case AsconWriteDone: if (a->noResponse) { a->state = AsconIdle; return AsconReady; } a->state = AsconReadStart; break; case AsconReading: if (result != 0) { break; /* char received: try again, probably more characters pending */ } return AsconPending; case AsconReadDone: a->state = AsconIdle; a->responseValid = 1; DynStringClear(a->errmsg); return AsconReady; case AsconReadDoneReconnect: a->responseValid = 1; DynStringClear(a->errmsg); AsconReconnect(a,NULL); return AsconReady; case AsconIdle: return AsconReady; case AsconTimeout: a->state = AsconIdle; return AsconFailure; case AsconFailed: now = DoubleTime(); if (a->fd > 0) { close(a->fd); lastClose = now; a->fd = -1; } if (a->reconnectInterval > 0 && now > a->lastReconnect + a->reconnectInterval) { a->lastReconnect = now; a->state = AsconConnectStart; } return AsconFailure; default: return AsconPending; } } } int AsconWrite(Ascon * a, char *command, int noResponse) { if (a->state != AsconIdle) { /* this might happen if a script is sending after an error */ return 0; } DynStringCopy(a->wrBuffer, command); a->noResponse = noResponse; a->state = AsconWriteStart; a->responseValid = 0; return AsconTask(a); } char *AsconRead(Ascon * a) { if (a->noResponse) { a->noResponse = 0; return NULL; } if (a->state != AsconIdle) { a->state = AsconIdle; return "programming error in devser.c/ascon.c"; } if (a->responseValid) { a->responseValid = 0; return GetCharArray(a->rdBuffer); } return NULL; } char *AsconGetError(Ascon *a) { return GetCharArray(a->errmsg); } int AsconLastState(Ascon *a) { return (int)a->state; } char *AsconHostport(Ascon *a) { if (a==NULL) { return NULL; } return a->hostport; } char *AsconIP(Ascon *a) { if (a==NULL) { return NULL; } return a->ip; } double AsconGetSetTimeout(Ascon *a, double timeout, int setmode) { if (setmode) { a->timeout = timeout; } return a->timeout; } int AsconReconnectInterval(Ascon *a, int interval) { if (interval >= 0) { a->reconnectInterval = interval; } return a->reconnectInterval; }