/*-------------------------------------------------------------------------- ANSTO Protocol Command Object Paul Hathaway, November, 2004 Copyright: See copyright.txt ----------------------------------------------------------------------------*/ #include #include #include #include #include #include #include #include #include #include #include #include "commandlog.h" #include "protocol.h" #include #define MAXMSG 1024 #define INIT_STR_SIZE 256 #define STR_RESIZE_LENGTH 256 #define NUMPROS 7 #define PROLISTLEN 8 typedef struct __Protocol { pObjectDescriptor pDes; /* required as first field */ char *name; /* protocol handler name */ char *version; /* protocol version string */ int iNumPros; /* number of valid protocols? */ writeFunc defaultWriter; /* default write function */ int isDefaultSet; char *pProList[PROLISTLEN]; /* list of valid protocols? */ } Protocol; char *pEventType[] = { "VALUECHANGE", /* 0 */ "MOTDRIVE", /* 1 */ "MONITOR", /* 2 */ "ROTSTART", /* 3 */ "ROTMOVE", /* 4 */ "SCANEND", /* 5 */ "SCANSTART", /* 6 */ "SCANPOINT", /* 7 */ "WLCHANGE", /* 8 */ "REFLECTIONDONE", /* 9 */ "COUNTSTART", /* 10 */ "COUNTEND", /* 11 */ "FILELOADED", /* 12 */ "MOTEND", /* 13 */ "BATCHSTART", /* 14 */ "BATCHAREA", /* 15 */ "BATCHEND", /* 16 */ "DRIVSTAT", /* 17 */ "STATUS", /* 18 */ "POSITION" /* 19 Motor position events, ffr */ }; char *pStatus[] = { "UNSET", "OKOK", /* 1 */ "HWIdle", /* 2 */ "HWBusy", /* 3 */ "HWFault", /* 4 */ "HWPosFault", /* 5 */ "HWCrash", /* 6 */ "NOMEMORY", /* 7 */ "HWNoBeam", /* 8 */ "HWPause", /* 9 */ "HWWarn", /* 10 */ "HWRedo", /* 11 */ }; typedef struct __Protocol *pProtocol; pProtocol CreateProtocol(void); static int ProtocolOptions(SConnection * pCon, pProtocol pPro); static int ProtocolHelp(SConnection * pCon, Protocol * pPro); static int ProtocolSet(SConnection * pCon, Protocol * pPro, char *pProName); static int ProtocolList(SConnection * pCon, Protocol * pPro); int ProtocolAction(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]); static int EnumChoice(char *pList[], int iLength, char *pInput); static int InitDefaultProtocol(SConnection * pCon, Protocol * pPro); /* Signatures for protocol writers implemented in this file */ int SCWriteSycamore(SConnection * pCon, char *pBuffer, int iOut); int SCWriteJSON_String(SConnection * pCon, char *pBuffer, int iOut); /* Signatures for protocols from conman.c*/ extern int SCAllWrite(SConnection * self, char *buffer, int iOut); /*--------------------------------------------------------------------------*/ pProtocol CreateProtocol(void) { int i, iNumPros = NUMPROS; char *pPros[] = { "default", "normal", "withcode", "sycamore", "json", "act", "all", NULL }; pProtocol pNew = NULL; pNew = (pProtocol) malloc(sizeof(Protocol)); if (!pNew) { return NULL; } pNew->pDes = CreateDescriptor("Protocol"); if (!pNew->pDes) { free(pNew); return NULL; } pNew->name = strdup("protocol"); pNew->version = strdup("1.0"); pNew->iNumPros = iNumPros; // pNew->pProList = (char *)malloc(sizeof(pPros)); for (i = 0; i < iNumPros; i++) { pNew->pProList[i] = strdup(pPros[i]); } pNew->pProList[i] = NULL; pNew->isDefaultSet = 0; return pNew; } /*-------------------------------------------------------------------------*/ void DeleteProtocol(void *self) { int i; pProtocol pOld = (pProtocol) self; if (NULL == pOld) { return; } if (pOld->name) { free(pOld->name); } if (pOld->pDes) { DeleteDescriptor(pOld->pDes); } if (pOld->version) { free(pOld->version); } if (pOld->pProList) { i = 0; while (NULL != pOld->pProList[i]) { free(pOld->pProList[i]); i++; } } free(pOld); } /*------------------------------------------------------------------*/ static int ContextDo(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { SConnection *comCon = NULL; char buffer[1024]; char *command; int status; if (argc < 3) { SCWrite(pCon, "ERROR: insufficient arguments to contextdo", eError); return 0; } comCon = SCCopyConnection(pCon); if (comCon == NULL) { SCWrite(pCon, "ERROR: out of memory in contextdo", eError); return 0; } status = Tcl_GetInt(pSics->pTcl, argv[1], &comCon->transID); if (status != TCL_OK) { snprintf(buffer, 1023, "ERROR: failed to convert %s to transaction ID", argv[1]); SCWrite(pCon, buffer, eError); return 0; } strlcpy(comCon->deviceID, argv[2], SCDEVIDLEN); memset(buffer, 0, sizeof(buffer)); command = Arg2Tcl(argc - 2, &argv[2], buffer, sizeof buffer); if (!command) { SCWrite(pCon, "ERROR: no more memory", eError); return 0; } status = InterpExecute(pSics, comCon, command); if (command != buffer) free(command); SCDeleteConnection(comCon); return status; } /*--------------------------------------------------------------------------*/ int InstallProtocol(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { pProtocol pNew = NULL; pNew = CreateProtocol(); if (NULL == pNew) { SCWrite(pCon, "No memory to create Protocol", eError); return 0; } AddCommand(pSics, "Protocol", ProtocolAction, DeleteProtocol, pNew); AddCommand(pSics, "contextdo", ContextDo, NULL, NULL); SCSendOK(pCon); return 1; } /*------------------------------------------------------------------------*/ void MakeProtocol(void) { pProtocol pNew = NULL; pNew = CreateProtocol(); if (NULL != pNew) { AddCommand(pServ->pSics, "Protocol", ProtocolAction, DeleteProtocol, pNew); AddCommand(pServ->pSics, "contextdo", ContextDo, NULL, NULL); } } /*------------------------------------------------------------------------*/ static int ProtocolOptions(SConnection * pCon, pProtocol pPro) { int i; char pBuffer[80]; for (i = 0; i < pPro->iNumPros; i++) { snprintf(pBuffer,sizeof(pBuffer)-1, "Protocol[%d] = %s", i, pPro->pProList[i]); SCWrite(pCon, pBuffer, eValue); } return 1; } /*------------------------------------------------------------------------*/ static int ProtocolHelp(SConnection * pCon, Protocol * pPro) { SCWrite(pCon, "Usage: protocol {help|list|options|reset} | set protocolName", eValue); return 1; } /*------------------------------------------------------------------------*/ static int ProtocolSet(SConnection * pCon, Protocol * pPro, char *pProName) { SConnection *pMaster = NULL; int proID; if (!SCVerifyConnection(pCon)) { return 0; } pMaster = SCfindMaster(pCon); assert(pMaster != NULL); /* lazy initialisation of defaultWriter since connection is verified */ InitDefaultProtocol(pCon, pPro); /* Do not die if no data */ if (NULL == pProName) { return 0; } /* check list of protocols for valid name and assign functions based */ /* on match of pProName */ proID = EnumChoice(pPro->pProList, pPro->iNumPros, pProName); switch (proID) { case -1: /* invalid */ return 0; break; case 1: /* normal (connection start default) */ SCSetWriteFunc(pMaster, SCNormalWrite); SCSetWriteFunc(pCon, SCNormalWrite); break; case 2: /* outcodes */ SCSetWriteFunc(pMaster, SCWriteWithOutcode); SCSetWriteFunc(pCon, SCWriteWithOutcode); break; case 3: /* sycamore */ SCSetWriteFunc(pMaster, SCWriteSycamore); SCSetWriteFunc(pCon, SCWriteSycamore); break; case 4: /* json */ SCSetWriteFunc(pCon, SCWriteJSON_String); SCSetWriteFunc(pMaster, SCWriteJSON_String); break; case 5: SCSetWriteFunc(pMaster, SCACTWrite); SCSetWriteFunc(pCon, SCACTWrite); break; case 6: SCSetWriteFunc(pMaster, SCAllWrite); SCSetWriteFunc(pCon, SCAllWrite); break; case 0: /* default = psi_sics */ default: SCSetWriteFunc(pMaster, pPro->defaultWriter); SCSetWriteFunc(pCon, pPro->defaultWriter); break; } pCon->iProtocolID = proID; pMaster->iProtocolID = proID; SCSendOK(pCon); return 1; } /*------------------------------------------------------------------------*/ int ProtocolGet(SConnection * pCon, void *pData, char *pProName, int len) { int Index; Protocol *pPro = (Protocol *) pData; if (!SCVerifyConnection(pCon)) { return 0; } if (pData == NULL) { pCon->iProtocolID = 0; return 1; } /* lazy initialisation of defaultWriter since connection is verified */ if (0 == pPro->isDefaultSet) { pPro->defaultWriter = SCGetWriteFunc(pCon); pPro->isDefaultSet = 1; pCon->iProtocolID = 0; } strlcpy(pProName, pPro->pProList[pCon->iProtocolID], len); return 1; #if 0 Index = pCon->iProtocolID; /* check list of protocols for valid name */ switch (Index) { case 0: /* default = psi_sics */ case 1: /* normal (connection start default) */ case 2: /* outcodes */ case 3: /* sycamore */ case 4: /* json */ case 5: /* act */ pProName = pPro->pProList[Index]; return 1; break; default: return 0; break; } #endif } /*------------------------------------------------------------------------*/ static int ProtocolList(SConnection * pCon, Protocol * pPro) { SCWrite(pCon, "Usage: protocol {help|list|options|reset} | set protocolName", eValue); return 1; } /*-------------------------------------------------------------------------*/ int ProtocolAction(SConnection * pCon, SicsInterp * pSics, void *pData, int argc, char *argv[]) { int iRet; char **argx; FuPaResult PaRes; pProtocol pPro = NULL; const int iNumCmds = 5; FuncTemplate CommandTemplate[] = { {"help", 0, {0, 0}}, {"list", 0, {0, 0}}, {"options", 0, {0, 0}}, {"set", 1, {FUPATEXT}}, {"reset", 0, {0, 0}}, {NULL} }; assert(pCon != NULL); assert(pSics != NULL); pPro = (pProtocol) pData; assert(pPro != NULL); /* You need to have User level access rights to use this facility */ if (!SCMatchRights(pCon, usSpy)) { return 0; } /* parse function args */ argtolower(argc, argv); argx = &argv[1]; iRet = EvaluateFuPa((pFuncTemplate) & CommandTemplate, iNumCmds, argc - 1, argx, &PaRes); /* if invalid (iRet < 0) then default to "help" command */ switch (iRet) { case 1: /* list */ iRet = ProtocolList(pCon, pPro); break; case 2: /* options */ iRet = ProtocolOptions(pCon, pPro); break; case 3: /* set */ iRet = ProtocolSet(pCon, pPro, PaRes.Arg[0].text); break; case 4: /* reset */ iRet = ProtocolSet(pCon, pPro, "default"); break; case 0: /* help */ default: iRet = ProtocolHelp(pCon, pPro); break; } return iRet; } /*-------------------------------------------------------------------------*/ static int EnumChoice(char *pList[], int iLength, char *pInput) { int i; int iRet = -1; for (i = 0; i < iLength; i++) { if (0 == strcmp(pInput, pList[i])) { iRet = i; break; } } return iRet; } /*----------------------------------*/ static int InitDefaultProtocol(SConnection * pCon, Protocol * pPro) { if (NULL == pCon) { return 0; } /* lazy initialisation of defaultWriter since connection is verified */ if (0 == pPro->isDefaultSet) { pPro->defaultWriter = SCGetWriteFunc(pCon); pPro->isDefaultSet = 1; pCon->iProtocolID = 0; } return pPro->isDefaultSet; } /*---------------------------------------------------------------------*/ void sycformat(char *tag, OutCode msgFlag, pDynString msgString, pDynString msgOut) { DynStringConcat(msgOut, " "); switch (msgFlag) { eEvent: break; eFinish: break; default: DynStringConcat(msgOut, tag); DynStringConcat(msgOut, "={"); DynStringConcat(msgOut, GetCharArray(msgString)); DynStringConcat(msgOut, "}"); break; } } int SCWriteSycamore(SConnection * pCon, char *pBuffer, int iOut) { int iRet; char pBueffel[MAXMSG]; long taskID = 0; /* char pPrefix[40];*/ pDynString pMsg = NULL; pDynString pMsgString = NULL; commandContext comCon; if (strlen(pBuffer) == 0) { return 0; } if (!SCVerifyConnection(pCon)) { return 0; } iRet=snprintf(pBueffel,sizeof(pBueffel)-1, "[%d]:%s", pCon->sockHandle, pBuffer); SICSLogWrite(pBueffel, iOut); if (iRet >= MAXMSG) { snprintf(pBueffel,sizeof(pBueffel)-1, "TRUNCATED MESSAGE: message len=%d, MAXMSG=%d",strlen(pBuffer), MAXMSG); SICSLogWrite(pBueffel, eError); } /* write to commandlog if user or manager privilege */ if (SCGetRights(pCon) <= usUser) { WriteToCommandLogId(NULL, iRet, pBuffer); } /* put it into the interpreter if present */ if (SCinMacro(pCon)) { InterpWrite(pServ->pSics, pBuffer); /* print it to client if error message */ /* FIXME should report errors via sycamore if((iOut== eError) || (iOut == eWarning) ) iRet = SCDoSockWrite(pCon,GetCharArray(pMsgOut)); */ } else { /* not in interpreter, normal logic */ comCon = SCGetContext(pCon); /* Return 0 without dying if no message data */ if (pBuffer == NULL) { return 0; } taskID = comCon.transID; pMsg = CreateDynString(INIT_STR_SIZE, STR_RESIZE_LENGTH); pMsgString = CreateDynString(INIT_STR_SIZE, STR_RESIZE_LENGTH); pBueffel[0] = '\0'; snprintf(pBueffel,sizeof(pBueffel)-1, "[con%4.4d:", (int) pCon->ident); /* field 1: connID */ DynStringConcat(pMsg, pBueffel); snprintf(pBueffel,sizeof(pBueffel)-1, "t%6.6d:", (int) taskID); /* field 2: taskID */ DynStringConcat(pMsg, pBueffel); /* deviceID */ DynStringConcat(pMsg, comCon.deviceID); DynStringConcatChar(pMsg, ':'); /* msgFlag */ switch (iOut) { case 5: /* eValue */ DynStringConcat(pMsg, "out"); break; default: DynStringConcat(pMsg, pCode[iOut]); break; } DynStringConcatChar(pMsg, ']'); if (iOut == eStart) { DynStringConcat(pMsgString, comCon.deviceID); } if (iOut == eEvent) { DynStringConcat(pMsgString, " type="); /* Default type to VALUECHANGE if conEventType not set */ if (-1 == pCon->conEventType) DynStringConcat(pMsgString, pEventType[0]); else DynStringConcat(pMsgString, pEventType[pCon->conEventType]); /* DynStringConcat(pMsgString, " status="); DynStringConcat(pMsgString, pStatus[pCon->conStatus]);*/ DynStringConcat(pMsgString, ","); } DynStringConcat(pMsgString, " "); DynStringConcat(pMsgString, pBuffer); sycformat(comCon.deviceID, iOut, pMsgString, pMsg); /* is this really to be printed ? */ if (iOut < pCon->iOutput) { if (pMsg != NULL) DeleteDynString(pMsg); return 0; } /* first the socket */ /*strcat(pMsg, pBueffel); */ iRet = SCDoSockWrite(pCon, GetCharArray(pMsg)); } if (pMsg != NULL) DeleteDynString(pMsg); return 1; } /* Only work for hipadaba commands, hlist, hset, hget, hnotify * A multiline string (ie have crnl) will be converted to an array. * Strings with '=' will be converted to name value pairs */ struct json_object *mkJSON_Object(SConnection * pCon, char *pBuffer, int iOut) { int linenum = __LINE__; char pBueffel[MAXMSG], *pBufferFrom, *pBufferTo; long taskID = 0; struct json_object *msg_json = NULL, *tmp_json = NULL; commandContext comCon; char pError[256]; pError[0] = '\0'; if (strlen(pBuffer) == 0) { return 0; } if (!SCVerifyConnection(pCon)) { return 0; } comCon = SCGetContext(pCon); /* Return 0 without dying if no message data */ if (pBuffer == NULL) { return 0; } /* build the Tcl-command to execute for formatting the data into a sycamore string */ taskID = comCon.transID; pBueffel[0] = '\0'; msg_json = json_object_new_object(); if (is_error(msg_json)) { linenum = __LINE__; goto reporterr; } /* field 1: connID */ json_object_object_add(msg_json, "con", json_object_new_int(pCon->ident)); /* field 2: taskID */ json_object_object_add(msg_json, "trans", json_object_new_int(taskID)); /* deviceID */ json_object_object_add(msg_json, "object", json_object_new_string(comCon.deviceID)); /* msgFlag */ switch (iOut) { case 5: /* eValue */ json_object_object_add(msg_json, "flag", json_object_new_string("out")); break; default: json_object_object_add(msg_json, "flag", json_object_new_string(pCode[iOut])); break; } if (iOut == eHdbValue || iOut == eHdbEvent) { tmp_json = json_tokener_parse(pBuffer); if (is_error(tmp_json)) { linenum = __LINE__; goto reporterr; } } else { /* Strip \r and \n */ for (pBufferFrom = pBufferTo = pBuffer;; pBufferFrom++) { if (*pBufferFrom == '\r' || *pBufferFrom == '\n') continue; pBufferTo = pBufferFrom; if (*pBufferTo == '\0') break; pBufferTo++; } tmp_json = json_object_new_string(pBuffer); if (is_error(tmp_json)) { linenum = __LINE__; goto reporterr; } } json_object_object_add(msg_json, "data", tmp_json); return msg_json; reporterr: SCSetWriteFunc(pCon, SCNormalWrite); snprintf(pError, 256, "{\"ERROR\": \"%s:%d Error making json object\"}", __FILE__, linenum); SCWrite(pCon, pError, eError); SCSetWriteFunc(pCon, SCWriteJSON_String); cleanup: if (tmp_json != NULL && !is_error(tmp_json)) json_object_put(tmp_json); if (msg_json != NULL && !is_error(msg_json)) json_object_put(msg_json); return NULL; } int SCWriteJSON_String(SConnection * pCon, char *pBuffer, int iOut) { struct json_object *my_object = NULL, *tmp_json = NULL; char pBueffel[MAXMSG], errBuff[MAXMSG]; int iRet, errLen = MAXMSG; if (strlen(pBuffer) == 0) return 1; if (!SCinMacro(pCon)) { iRet=snprintf(pBueffel,sizeof(pBueffel)-1, "[%d]:%s", pCon->sockHandle, pBuffer); SICSLogWrite(pBueffel, iOut); if (iRet >= MAXMSG) { snprintf(pBueffel,sizeof(pBueffel)-1, "TRUNCATED MESSAGE: message len=%d, MAXMSG=%d",strlen(pBuffer), MAXMSG); SICSLogWrite(pBueffel, eError); } } /* write to commandlog if user or manager privilege */ if (SCGetRights(pCon) <= usUser) { if (pCon->iMacro != 1) { WriteToCommandLogId(NULL, iRet, pBuffer); } else { if (iOut == eError || iOut == eWarning) { WriteToCommandLogId(NULL, iRet, pBuffer); } } } if (SCinMacro(pCon)) { InterpWrite(pServ->pSics, pBuffer); /* print it to client if error message */ if ((iOut == eError) || (iOut == eWarning)) { tmp_json = json_object_new_string(pBuffer); iRet = SCDoSockWrite(pCon, (char *)json_object_to_json_string(tmp_json)); } } else { if ((my_object = mkJSON_Object(pCon, pBuffer, iOut)) == NULL) { snprintf(errBuff, errLen, "failed to make JSON object from, %s", pBuffer); tmp_json = json_object_new_string(errBuff); my_object = json_object_new_object(); json_object_object_add(my_object, "ERROR", tmp_json); SCDoSockWrite(pCon, (char *)json_object_to_json_string(my_object)); iRet = 0; } else { iRet = SCDoSockWrite(pCon, (char *)json_object_to_json_string(my_object)); } } if (tmp_json != NULL && !is_error(tmp_json)) json_object_put(tmp_json); if (my_object != NULL && !is_error(my_object)) json_object_put(my_object); return iRet; } /*------------------------------------------------------------------------*/ /* Protocol API */ char *GetProtocolName(SConnection * pCon) { pProtocol pPro; pSicsInterp pSics; if (!SCVerifyConnection(pCon)) { return NULL; } pSics = GetInterpreter(); if (!pSics) return NULL; pPro = FindCommandData(pSics, "protocol", "Protocol"); if (!pPro) return NULL; InitDefaultProtocol(pCon, pPro); /* check list of protocols for valid name */ switch (pCon->iProtocolID) { case 0: /* default = psi_sics */ case 1: /* normal (connection start default) */ case 2: /* outcodes */ case 3: /* sycamore */ case 4: /* json */ return strdup(pPro->pProList[pCon->iProtocolID]); break; default: return strdup("invalid"); break; } } /*----------------------------------*/ int GetProtocolID(SConnection * pCon) { if (NULL != pCon) { return pCon->iProtocolID; } return -1; } /*---------------------------------------------------------------------------*/ writeFunc GetProtocolWriteFunc(SConnection * pCon) { if (pCon != NULL) { switch (pCon->iProtocolID) { case 2: /* outcodes */ return SCWriteWithOutcode; break; case 3: /* sycamore */ return SCWriteSycamore; break; case 4: /* json */ return SCWriteJSON_String; break; case 5: return SCACTWrite; break; default: return SCNormalWrite; break; } } return SCNormalWrite; }