From de134ea72db3192c3bd7d22667a097fe912653fc Mon Sep 17 00:00:00 2001 From: Michael Davidsaver Date: Fri, 27 Apr 2012 13:21:51 -0400 Subject: [PATCH] Added chfPlugin, a simple interface for channel filter plugins. - chfPlugin.h contains the simple jump table and doxygen documentation - chfPlugin.c is a wrapper library around the yajl callbacks that parses any client-supplied configuration into the user's structure - test/chfPluginTest.c tests the library and the data conversion --- src/ioc/db/Makefile | 2 + src/ioc/db/chfPlugin.c | 613 ++++++++++++++++++++++++++++++++ src/ioc/db/chfPlugin.h | 223 ++++++++++++ src/ioc/db/test/Makefile | 5 + src/ioc/db/test/chfPluginTest.c | 388 ++++++++++++++++++++ 5 files changed, 1231 insertions(+) create mode 100644 src/ioc/db/chfPlugin.c create mode 100644 src/ioc/db/chfPlugin.h create mode 100644 src/ioc/db/test/chfPluginTest.c diff --git a/src/ioc/db/Makefile b/src/ioc/db/Makefile index 132861fda..3a47dae3e 100644 --- a/src/ioc/db/Makefile +++ b/src/ioc/db/Makefile @@ -32,6 +32,7 @@ INC += db_field_log.h INC += initHooks.h INC += recGbl.h INC += dbIocRegister.h +INC += chfPlugin.h INC += db_access_routines.h INC += db_convert.h @@ -78,4 +79,5 @@ dbCore_SRCS += dbPutNotifyBlocker.cpp dbCore_SRCS += dbContextReadNotifyCache.cpp dbCore_SRCS += templateInstances.cpp dbCore_SRCS += dbIocRegister.c +dbCore_SRCS += chfPlugin.c diff --git a/src/ioc/db/chfPlugin.c b/src/ioc/db/chfPlugin.c new file mode 100644 index 000000000..107c4a622 --- /dev/null +++ b/src/ioc/db/chfPlugin.c @@ -0,0 +1,613 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +/* Based on the linkoptions utility by Michael Davidsaver (BNL) */ + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#define epicsExportSharedSymbols +#include "chfPlugin.h" + +#ifndef HUGE_VALF +# define HUGE_VALF HUGE_VAL +#endif +#ifndef HUGE_VALL +# define HUGE_VALL (-(HUGE_VAL)) +#endif + +/* + * Data for a chfPlugin + */ +typedef struct chfPluginCtx { + const chfPluginArgDef *opts; + size_t nopts; + const chfPluginIf *pif; +} chfPluginCtx; + +/* + * Parser state data for a chfFilter (chfPlugin instance) + */ +typedef struct chfFilterCtx { + const chfPluginCtx *plugin; + epicsUInt32 *found; + void *puser; + epicsInt16 nextParam; +} chfFilterCtx; + +/* Data types we get from the parser */ +typedef enum chfPluginType { + chfPluginTypeBool, + chfPluginTypeInt, + chfPluginTypeDouble, + chfPluginTypeString +} chfPluginType; + +/* + * Convert the (long) integer value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static +int +store_integer_value(const chfPluginArgDef *opt, void *user, long val) +{ + long *ival; + int *eval; + const chfPluginEnumType *emap; + double *dval; + char *sval; + char buff[22]; /* 2^64 = 1.8e+19, so 20 digits plus sign max */ + +/* printf("Got an integer for %s (type %d): %ld\n", opt->name, opt->optType, val); */ + + if (!opt->convert && opt->optType != chfPluginArgInt32) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (long*) ((char*)user + opt->offset); + *ival = val; + break; + case chfPluginArgBoolean: + eval = (int*) ((char*)user + opt->offset); + *eval = !!val; + break; + case chfPluginArgDouble: + dval = (double*) ((char*)user + opt->offset); + *dval = (double) val; + break; + case chfPluginArgString: + sval = ((char*)user + opt->offset); + sprintf(buff, "%ld", val); + if (strlen(buff) > opt->size-1) { + return -1; + } + strncpy(sval, buff, opt->size-1); + sval[opt->size-1]='\0'; + break; + case chfPluginArgEnum: + eval = (int*) ((char*)user + opt->offset); + for (emap = opt->enums; emap && emap->name; emap++) { + if (val == emap->value) { + *eval = val; + break; + } + } + if (!emap || !emap->name) { + return -1; + } + break; + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the (int) boolean value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static +int +store_boolean_value(const chfPluginArgDef *opt, void *user, int val) +{ + long *ival; + int *eval; + double *dval; + char *sval; + +/* printf("Got a boolean for %s (type %d): %d\n", opt->name, opt->optType, val); */ + + if (!opt->convert && opt->optType != chfPluginArgBoolean) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (long*) ((char*)user + opt->offset); + *ival = val; + break; + case chfPluginArgBoolean: + eval = (int*) ((char*)user + opt->offset); + *eval = val; + break; + case chfPluginArgDouble: + dval = (double*) ((char*)user + opt->offset); + *dval = val ? 1. : 0.; + break; + case chfPluginArgString: + sval = ((char*)user + opt->offset); + if ((val ? 4 : 5) > opt->size-1) { + return -1; + } + strncpy(sval, (val ? "true" : "false"), opt->size-1); + sval[opt->size-1]='\0'; + break; + case chfPluginArgEnum: + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the double value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static +int +store_double_value(const chfPluginArgDef *opt, void *user, double val) +{ + long *ival; + int *eval; + double *dval; + char *sval; + int i; + +/* + printf("Got a double for %s (type %d, convert: %s): %g\n", + opt->name, opt->optType, opt->convert?"yes":"no", val); +*/ + if (!opt->convert && opt->optType != chfPluginArgDouble) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + if (val < LONG_MIN || val > LONG_MAX) { + return -1; + } + ival = (long*) ((char*)user + opt->offset); + *ival = (long) val; + break; + case chfPluginArgBoolean: + eval = (int*) ((char*)user + opt->offset); + *eval = (val != 0.) ? 1 : 0; + break; + case chfPluginArgDouble: + dval = (double*) ((char*)user + opt->offset); + *dval = val; + break; + case chfPluginArgString: + sval = ((char*)user + opt->offset); + if (opt->size <= 8) { /* Play it safe: 3 exp + 2 sign + 'e' + '.' */ + return -1; + } + i = snprintf(sval, opt->size, "%.*g", opt->size - 7, val); + if (i >= opt->size) { + return -1; + } + break; + case chfPluginArgEnum: + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +/* + * Convert the (char*) string value 'val' to the type named in 'opt->optType' + * and store the result at 'user + opt->offset'. + */ +static +int +store_string_value(const chfPluginArgDef *opt, void *user, const char *val, size_t len) +{ + long *ival; + int *eval; + const chfPluginEnumType *emap; + double *dval; + char *sval; + char *end; + int i; + +/* printf("Got a string for %s (type %d): %.*s\n", opt->name, opt->optType, len, val); */ + + if (!opt->convert && opt->optType != chfPluginArgString && opt->optType != chfPluginArgEnum) { + return -1; + } + + switch (opt->optType) { + case chfPluginArgInt32: + ival = (long*) ((char*)user + opt->offset); + *ival = strtol(val, &end, 0); + /* test for the myriad error conditions which strtol may use */ + if (*ival == LONG_MAX || *ival == LONG_MIN || end == val) { + return -1; + } + break; + case chfPluginArgBoolean: + eval = (int*) ((char*)user + opt->offset); + if (strncasecmp(val, "true", len) == 0) { + *eval = 1; + } else if (strncasecmp(val, "false", len) == 0) { + *eval = 0; + } else { + i = strtol(val, &end, 0); + if (i > INT_MAX || i < INT_MIN || end == val) { + return -1; + } + *eval = !!i; + } + break; + case chfPluginArgDouble: + dval = (double*) ((char*)user + opt->offset); + *dval = strtod(val, &end); + /* Indicates errors in the same manner as strtol */ + if (*dval==HUGE_VALF||*dval==HUGE_VALL||end==val ) + { + return -1; + } + break; + case chfPluginArgString: + i = opt->size-1 < len ? opt->size-1 : len; + sval = ((char*)user + opt->offset); + strncpy(sval, val, i); + sval[i] = '\0'; + break; + case chfPluginArgEnum: + eval = (int*) ((char*)user + opt->offset); + for (emap = opt->enums; emap && emap->name; emap++) { + if (strncmp(emap->name, val, len) == 0) { + *eval = emap->value; + break; + } + } + if( !emap || !emap->name ) { + return -1; + } + break; + case chfPluginArgInvalid: + return -1; + } + return 0; +} + +static void freeInstanceData(chfFilterCtx *fctx) +{ + free(fctx->found); /* FIXME: Use a free-list */ + free(fctx); /* FIXME: Use a free-list */ +} + +/* + * chFilterIf callbacks + */ + +/* First entry point when a new filter instance is created. + * All per-instance allocations happen here. + */ +static parse_result parse_start(chFilter *filter) +{ + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx; + + /* Filter context */ + /* FIXME: Use a free-list */ + fctx = calloc(1, sizeof(chfFilterCtx)); + if (!fctx) { + fprintf(stderr,"chfFilterCtx calloc failed\n"); + goto errfctx; + } + fctx->nextParam = -1; + + /* Bit array to find missing required keys */ + /* FIXME: Use a free-list */ + fctx->found = calloc( (pctx->nopts/32)+1, sizeof(epicsUInt32) ); + if (!fctx->found) { + fprintf(stderr,"chfConfigParseStart: bit array calloc failed\n"); + goto errbitarray; + } + + /* Call the plugin to allocate its structure, it returns NULL on error */ + if (pctx->pif->allocPvt) { + if ((fctx->puser = pctx->pif->allocPvt()) == NULL) + goto errplugin; + } + + filter->puser = (void*) fctx; + + return parse_continue; + + errplugin: + free(fctx->found); /* FIXME: Use a free-list */ + errbitarray: + free(fctx); /* FIXME: Use a free-list */ + errfctx: + return parse_stop; +} + +static void parse_abort(chFilter *filter) { + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx = (chfFilterCtx*) filter->puser; + + /* Call the plugin to tell it we're aborting */ + if (pctx->pif->parse_error) pctx->pif->parse_error(); + if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser); + freeInstanceData(fctx); +} + +static parse_result parse_end(chFilter *filter) +{ + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx = (chfFilterCtx*) filter->puser; + const chfPluginArgDef *cur; + int i; + + for(cur = pctx->opts, i = 0; cur && cur->name; cur++, i++) { + if ( !(fctx->found[i/32] & (1 << (i%32))) && cur->required ) { + if (pctx->pif->parse_error) pctx->pif->parse_error(); + if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser); + freeInstanceData(fctx); + return parse_stop; + } + } + + /* Call the plugin to tell it we're done */ + if (pctx->pif->parse_ok) { + if (pctx->pif->parse_ok(fctx->puser)) { + if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser); + freeInstanceData(fctx); + return parse_stop; + } + } + + return parse_continue; +} + +static parse_result parse_boolean(chFilter *filter, int boolVal) +{ + const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts; + chfFilterCtx *ctx = (chfFilterCtx*)filter->puser; + + if (ctx->nextParam < 0 || store_boolean_value(&opts[ctx->nextParam], ctx->puser, boolVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_integer(chFilter *filter, long integerVal) +{ + const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts; + chfFilterCtx *ctx = (chfFilterCtx*)filter->puser; + + if (ctx->nextParam < 0 || store_integer_value(&opts[ctx->nextParam], ctx->puser, integerVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_double(chFilter *filter, double doubleVal) +{ + const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts; + chfFilterCtx *ctx = (chfFilterCtx*)filter->puser; + + if (ctx->nextParam < 0 || store_double_value(&opts[ctx->nextParam], ctx->puser, doubleVal)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_string(chFilter *filter, const char *stringVal, size_t stringLen) +{ + const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts; + chfFilterCtx *ctx = (chfFilterCtx*)filter->puser; + + if (ctx->nextParam < 0 || store_string_value(&opts[ctx->nextParam], ctx->puser, stringVal, stringLen)) { + return parse_stop; + } else { + return parse_continue; + } +} + +static parse_result parse_start_map(chFilter *filter) +{ + return parse_continue; +} + +static parse_result parse_map_key(chFilter *filter, const char *key, size_t stringLen) +{ + const chfPluginArgDef *cur; + const chfPluginArgDef *opts = ((chfPluginCtx*)filter->plug->puser)->opts; + chfFilterCtx *ctx = (chfFilterCtx*)filter->puser; + int i; + + ctx->nextParam = -1; + for(cur = opts, i = 0; cur && cur->name; cur++, i++) { + if (strncmp(key, cur->name, stringLen) == 0) { + ctx->nextParam = i; + break; + } + } + if (ctx->nextParam == -1) { + return parse_stop; + } + + ctx->found[i/32] |= 1<<(i%32); + return parse_continue; +} + +static parse_result parse_end_map(chFilter *filter) +{ + return parse_continue; +} + +static long channel_open(chFilter *filter) +{ + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx = (chfFilterCtx*) filter->puser; + + if (pctx->pif->channel_open) return pctx->pif->channel_open(filter->chan, fctx->puser); + else return 0; +} + +static void channel_report(chFilter *filter, const char *intro, int level) +{ + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx = (chfFilterCtx*) filter->puser; + + if (pctx->pif->channel_report) pctx->pif->channel_report(filter->chan, fctx->puser, intro, level); +} + +static void channel_close(chFilter *filter) +{ + chfPluginCtx *pctx = (chfPluginCtx*) filter->plug->puser; + chfFilterCtx *fctx = (chfFilterCtx*) filter->puser; + + if (pctx->pif->channel_close) pctx->pif->channel_close(filter->chan, fctx->puser); + if (pctx->pif->freePvt) pctx->pif->freePvt(fctx->puser); + free(fctx->found); /* FIXME: Use a free-list */ + free(fctx); /* FIXME: Use a free-list */ +} + +/* + * chFilterIf for the wrapper + * we just support a simple one-level map, and no arrays + */ +static chFilterIf wrapper_fif = { + parse_start, + parse_abort, + parse_end, + + NULL, /* parse_null, */ + parse_boolean, + parse_integer, + parse_double, + parse_string, + + parse_start_map, + parse_map_key, + parse_end_map, + + NULL, /* parse_start_array, */ + NULL, /* parse_end_array, */ + + channel_open, + channel_report, + channel_close +}; + +epicsShareFunc +const char* +epicsShareAPI +chfPluginEnumString(const chfPluginEnumType *emap, int i, const char* def) +{ + for(; emap && emap->name; emap++) { + if ( i == emap->value ) { + return emap->name; + } + } + return def; +} + +epicsShareFunc +int +epicsShareAPI +chfPluginRegister(const char* key, const chfPluginIf *pif, const chfPluginArgDef* opts) +{ + chfPluginCtx *pctx; + size_t i; + const chfPluginArgDef *cur; + + /* Check and count options */ + for (i = 0, cur = opts; cur && cur->name; i++, cur++) { + switch(cur->optType) { + case chfPluginArgInt32: + if (cur->size < sizeof(long)) { + errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for long (%lu)\n", + key, cur->size, cur->name, + (unsigned long) sizeof(long)); + return -1; + } + break; + case chfPluginArgBoolean: + if (cur->size < 1) { + errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for boolean (%lu)\n", + key, cur->size, cur->name, + (unsigned long) sizeof(char)); + return -1; + } + break; + case chfPluginArgDouble: + if (cur->size < sizeof(double)) { + errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for double (%lu)\n", + key, cur->size, cur->name, + (unsigned long) sizeof(double)); + return -1; + } + break; + case chfPluginArgString: + if (cur->size < sizeof(char*)) { + /* Catch if someone has given us a char* instead of a char[] + * Also means that char buffers must be >=4. + */ + errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for string (>= %lu)\n", + key, cur->size, cur->name, + (unsigned long) sizeof(char*)); + return -1; + } + break; + case chfPluginArgEnum: + if (cur->size < sizeof(int)) { + errlogPrintf("Plugin %s: provided storage (%d bytes) for %s is too small for enum (%lu)\n", + key, cur->size, cur->name, + (unsigned long) sizeof(int)); + return -1; + } + break; + case chfPluginArgInvalid: + errlogPrintf("Plugin %s: storage type for %s is not defined\n", + key, cur->name); + return -1; + break; + } + } + + /* Plugin context */ + pctx = dbCalloc(1, sizeof(chfPluginCtx)); + pctx->pif = pif; + pctx->opts = opts; + pctx->nopts = i; + + dbRegisterFilter(key, &wrapper_fif, pctx); + + return 0; +} diff --git a/src/ioc/db/chfPlugin.h b/src/ioc/db/chfPlugin.h new file mode 100644 index 000000000..43669c4a5 --- /dev/null +++ b/src/ioc/db/chfPlugin.h @@ -0,0 +1,223 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#ifndef CHFPLUGIN_H +#define CHFPLUGIN_H + +#include +#include +#include +#include + +/* Based on the linkoptions utility by Michael Davidsaver (BNL) */ + +/** @file chfPlugin.h + * @brief Channel filter simplified plugins. + * + * Utility layer to allow an easier (reduced) interface for + * channel filter plugins. + * + * Parsing the configuration arguments of a channel filter plugin + * is done according to an argument description table provided by the plugin. + * The parser stores the results directly into a user supplied structure + * after appropriate type conversion. + * + * To specify the arguments, a chfPluginArgDef table must be defined + * for the user structure. This table has to be specified when the plugin registers. + * + * The plugin is responsible to register an init function using + * epicsExportRegistrar() and the accompanying registrar() directive in the dbd, + * and call chfPluginRegister() from within the init function. + * + * For example: + * + * typedef struct myStruct { + * ... other stuff + * epicsUInt32 ival; + * double dval; + * epicsUInt32 ival2; + * int enumval; + * char strval[20]; + * } myStruct; + * + * static const + * chfPluginEnumType colorEnum[] = { {"Red",1}, {"Green",2}, {"Blue",3}, {NULL,0} }; + * + * static const + * chfPluginDef myStructDef[] = { + * chfInt32 (myStruct, ival, "Integer" , 0, 0), + * chfInt32 (myStruct, ival2, "Second" , 1, 0), + * chfDouble(myStruct, dval, "Double" , 1, 0), + * chfString(myStruct, strval , "String" , 1, 0), + * chfEnum (myStruct, enumval, "Color" , 1, 0, colorEnum), + * chfPluginEnd + * }; + * + * Note: The 4th argument specifies the parameter to be required (1) or optional (0), + * the 5th whether converting to the required type is allowed (1), or + * type mismatches are an error (0). + * + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** @brief Channel filter simplified plugin interface. + * + * The routines in this structure must be implemented by each filter plugin. + */ +typedef struct chfPluginIf { + + /* Memory management */ + /** @brief Allocate private resources. + * + * Called before parsing starts. + * The plugin should allocate its per-instance structures, + * returning a pointer to them or NULL requesting an abort of the operation. + * + * allocPvt may be set to NULL, if no resource allocation is needed. + * + * @return Pointer to private structure, NULL if operation is to be aborted. + */ + void * (* allocPvt) (void); + + /** @brief Free private resources. + * + * Called as part of abort or shutdown. + * The plugin should release any resources allocated for this filter; + * no further calls through this interface will be made. + * + * freePvt may be set to NULL, if no resources need to be released. + * + * @param pvt Pointer to private structure. + */ + void (* freePvt) (void *pvt); + + /* Parameter parsing results */ + /** @brief A parsing error occurred. + * + * Called after parsing failed with an error. + */ + void (* parse_error) (void); + + /** @brief Configuration has been parsed successfully. + * + * Called after parsing has finished ok. + * The plugin may check the validity of the parsed data, + * returning -1 to request an abort of the operation. + * + * @param pvt Pointer to private structure. + * @return 0 for success, -1 if operation is to be aborted. + */ + int (* parse_ok) (void *pvt); + + /* Channel operations */ + /** @brief Open channel. + * + * Called as part of the channel connection setup. + * @param chan dbChannel for which the connection is being made. + * @param pvt Pointer to private structure. + * @return 0 for success, -1 if operation is to be aborted. + */ + long (* channel_open) (dbChannel *chan, void *pvt); + + /** @brief Channel report request. + * + * Called as part of show... routines. + * @param chan dbChannel for which the report is requested. + * @param pvt Pointer to private structure. + * @param intro Line header string to be printed at the beginning of each line. + * @param level Interest level. + */ + void (* channel_report) (dbChannel *chan, void *pvt, const char *intro, int level); + + /* FIXME: More filter routines here ... */ + + /** @brief Channel close request. + * + * Called as part of connection shutdown. + * @param chan dbChannel for which the connection is being shut down. + * @param pvt Pointer to private structure. + */ + void (* channel_close) (dbChannel *chan, void *pvt); + +} chfPluginIf; + +typedef enum chfPluginArg { + chfPluginArgInvalid=0, + chfPluginArgBoolean, + chfPluginArgInt32, + chfPluginArgDouble, + chfPluginArgString, + chfPluginArgEnum +} chfPluginArg; + +typedef struct chfPluginEnumType { + const char *name; + const int value; +} chfPluginEnumType; + +typedef struct chfPluginArgDef { + const char * name; + chfPluginArg optType; + int required:1; + int convert:1; + epicsUInt32 offset; + epicsUInt32 size; + const chfPluginEnumType *enums; +} chfPluginArgDef; + +#define chfInt32(Struct, Member, Name, Req, Conv) \ +{Name, chfPluginArgInt32, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfBoolean(Struct, Member, Name, Req, Conv) \ +{Name, chfPluginArgBoolean, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfDouble(Struct, Member, Name, Req, Conv) \ +{Name, chfPluginArgDouble, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfString(Struct, Member, Name, Req, Conv) \ +{Name, chfPluginArgString, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), NULL} + +#define chfEnum(Struct, Member, Name, Req, Conv, Enums) \ +{Name, chfPluginArgEnum, Req, Conv, OFFSET(Struct, Member), sizeof( ((Struct*)0)->Member ), Enums} + +#define chfPluginArgEnd {0} + +/* Extra output when parsing and converting */ +#define CHFPLUGINDEBUG 1 + +/** @brief Return the string associated with Enum index 'i'. + * + * @param Enums A null-terminated array of string/integer pairs. + * @param i An Enum index. + * @param def String to be returned when 'i' isn't a valid Enum index. + * @return The string associated with 'i'. + */ +epicsShareFunc +const char* +epicsShareAPI +chfPluginEnumString(const chfPluginEnumType *Enums, int i, const char* def); + +/** @brief Register a plugin. + * + * @param key The plugin name key that clients will use. + * @param pif Pointer to the plugin's interface. + * @param opts Pointer to the configuration argument description table. + */ +epicsShareFunc +int +epicsShareAPI +chfPluginRegister(const char* key, const chfPluginIf *pif, const chfPluginArgDef* opts); + +#ifdef __cplusplus +} +#endif + +#endif // CHFPLUGIN_H diff --git a/src/ioc/db/test/Makefile b/src/ioc/db/test/Makefile index a1bc70d5e..bed566e824 100644 --- a/src/ioc/db/test/Makefile +++ b/src/ioc/db/test/Makefile @@ -25,6 +25,11 @@ dbChannelTest_SRCS += dbChannelTest.c OBJS_IOC_vxWorks += dbChannelTest TESTS += dbChannelTest +TESTPROD_HOST += chfPluginTest +chfPluginTest_SRCS += chfPluginTest.c +OBJS_IOC_vxWorks += chfPluginTest +TESTS += chfPluginTest + TESTSCRIPTS_HOST += $(TESTS:%=%.t) include $(TOP)/configure/RULES diff --git a/src/ioc/db/test/chfPluginTest.c b/src/ioc/db/test/chfPluginTest.c new file mode 100644 index 000000000..44609aee6 --- /dev/null +++ b/src/ioc/db/test/chfPluginTest.c @@ -0,0 +1,388 @@ +/*************************************************************************\ +* Copyright (c) 2010 Brookhaven National Laboratory. +* Copyright (c) 2010 Helmholtz-Zentrum Berlin +* fuer Materialien und Energie GmbH. +* EPICS BASE is distributed subject to a Software License Agreement found +* in file LICENSE that is included with this distribution. +\*************************************************************************/ + +#include + +#include "chfPlugin.h" +#include "dbStaticLib.h" +#include "dbAccessDefs.h" +#include "epicsUnitTest.h" +#include "testMain.h" + +#define PATTERN 0x55555555 + +/* Expected call bit definitions */ +#define e_alloc 0x00000001 +#define e_free 0x00000002 +#define e_error 0x00000004 +#define e_ok 0x00000008 +#define e_open 0x00000010 +#define e_report 0x00000020 +#define e_close 0x00000040 + +unsigned int e, c; + +#define e_any (e_alloc | e_free | e_error | e_ok | e_open | e_report | e_close) + +typedef struct myStruct { + int sent1; + char flag; + int sent2; + epicsUInt32 ival; + int sent3; + double dval; + int sent4; + int enumval; + int sent5; + char str[20]; + int sent6; + char c; + char c1[2]; +} myStruct; + +static const +chfPluginEnumType colorEnum[] = { {"R", 1}, {"G", 2}, {"B", 4}, {NULL,0} }; + +static const +chfPluginArgDef strictOpts[] = { + chfInt32 (myStruct, ival, "i" , 1, 0), + chfBoolean(myStruct, flag, "f" , 1, 0), + chfDouble (myStruct, dval, "d" , 1, 0), + chfString (myStruct, str, "s" , 1, 0), + chfEnum (myStruct, enumval, "c" , 1, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef noconvOpts[] = { + chfInt32 (myStruct, ival, "i" , 0, 0), + chfBoolean(myStruct, flag, "f" , 0, 0), + chfDouble (myStruct, dval, "d" , 0, 0), + chfString (myStruct, str, "s" , 0, 0), + chfEnum (myStruct, enumval, "c" , 0, 0, colorEnum), + chfPluginArgEnd +}; + +static const +chfPluginArgDef sloppyOpts[] = { + chfInt32 (myStruct, ival, "i" , 0, 1), + chfBoolean(myStruct, flag, "f" , 0, 1), + chfDouble (myStruct, dval, "d" , 0, 1), + chfString (myStruct, str, "s" , 0, 1), + chfEnum (myStruct, enumval, "c" , 0, 1, colorEnum), + chfPluginArgEnd +}; + +/* Options defs with not enough room provided */ +static const +chfPluginArgDef brokenOpts1[] = { + chfInt32 (myStruct, c, "i" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts2[] = { + chfDouble(myStruct, c, "d" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts3[] = { + chfString(myStruct, c1, "s" , 0, 1), + chfPluginArgEnd +}; + +static const +chfPluginArgDef brokenOpts4[] = { + chfEnum (myStruct, c, "c" , 0, 1, colorEnum), + chfPluginArgEnd +}; + +int p_ok_return = 0; +int c_open_return = 0; +void *puser; + +static void clearStruct(void *p) { + myStruct *my = (myStruct*) p; + + if (!my) return; + memset(my, 0, sizeof(myStruct)); + my->sent1 = my->sent2 = my->sent3 = my->sent4 = my->sent5 = my->sent6 = PATTERN; + my->ival = 12; + my->flag = 1; + my->dval = 1.234e5; + strcpy(my->str, "hello"); + my->enumval = 4; +} + +static void * allocPvt(void) +{ + testOk(e & e_alloc, "allocPvt called"); + c |= e_alloc; + + myStruct *my = puser = (myStruct*) calloc(1, sizeof(myStruct)); + clearStruct (my); + return my; +} + +static void freePvt(void *user) +{ + testOk(e & e_free, "freePvt called"); + c |= e_free; + testOk(user == puser, "user pointer correct"); + + free(user); + puser = NULL; +} + +static void parse_error(void) +{ + testOk(e & e_error, "parse_error called"); + c |= e_error; +} + +static int parse_ok(void *user) +{ + testOk(e & e_ok, "parse_ok called"); + c |= e_ok; + testOk(user == puser, "user pointer correct"); + + return p_ok_return; +} + +static long channel_open(dbChannel *chan, void *user) +{ + testOk(e & e_open, "channel_open called"); + c |= e_open; + testOk(user == puser, "user pointer correct"); + + return c_open_return; +} + +static void channel_report(dbChannel *chan, void *user, const char *intro, int level) +{ + testOk(e & e_report, "channel_report called"); + c |= e_report; + testOk(user == puser, "user pointer correct"); +} + +static void channel_close(dbChannel *chan, void *user) +{ + testOk(e & e_close, "channel_close called"); + c |= e_close; + testOk(user == puser, "user pointer correct"); +} + +static chfPluginIf myPif = { + allocPvt, + freePvt, + + parse_error, + parse_ok, + + channel_open, + channel_report, + channel_close +}; + +static int checkValues(myStruct *my, epicsUInt32 i, int f, double d, char *s, int c) { + if (!my) return 0; + if (my->sent1 == PATTERN && my->sent2 == PATTERN && my->sent3 == PATTERN + && my->sent4 == PATTERN && my->sent5 == PATTERN && my->sent6 == PATTERN + && my->ival == i && my->flag == f && my->dval == d && my->enumval == c + && (strcmp(s, my->str) == 0)) { + return 1; + } else { + return 0; + } +} + +MAIN(chfPluginTest) +{ + dbChannel *pch; + + testPlan(667); + + /* Enum to string conversion */ + testOk(strcmp(chfPluginEnumString(colorEnum, 1, "-"), "R") == 0, "Enum to string: R"); + testOk(strcmp(chfPluginEnumString(colorEnum, 2, "-"), "G") == 0, "Enum to string: G"); + testOk(strcmp(chfPluginEnumString(colorEnum, 4, "-"), "B") == 0, "Enum to string: B"); + testOk(strcmp(chfPluginEnumString(colorEnum, 3, "-"), "-") == 0, "Enum to string: invalid index"); + + testOk1(!dbReadDatabase(&pdbbase, "dbChannelTest.dbx", ".:..", NULL)); + testOk(!!pdbbase, "pdbbase was set"); + + testOk(chfPluginRegister("buggy", &myPif, brokenOpts1), "not enough storage for integer"); + testOk(chfPluginRegister("buggy", &myPif, brokenOpts2), "not enough storage for double"); + testOk(chfPluginRegister("buggy", &myPif, brokenOpts3), "not enough storage for string"); + testOk(chfPluginRegister("buggy", &myPif, brokenOpts4), "not enough storage for enum"); + + testOk(!chfPluginRegister("strict", &myPif, strictOpts), "register plugin strict"); + testOk(!chfPluginRegister("noconv", &myPif, noconvOpts), "register plugin noconv"); + testOk(!chfPluginRegister("sloppy", &myPif, sloppyOpts), "register plugin sloppy"); + + /* STRICT parsing: mandatory, no conversion */ + + /* All perfect */ + e = e_alloc | e_ok; c = 0; + testOk(!!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "strict parsing: JSON correct"); + testOk(checkValues(puser, 1, 0, 1.2e15, "bar", 1), "guards intact, values correct"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_close | e_free; c = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + + /* Any one missing must fail */ + e = e_alloc | e_error | e_free; c = 0; + testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), "strict parsing: c missing"); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_alloc | e_error | e_free; c = 0; + testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"f\":false,\"i\":1,\"d\":1.2e15,\"c\":\"R\"}}")), "strict parsing: s missing"); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_alloc | e_error | e_free; c = 0; + testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"i\":1,\"c\":\"R\",\"f\":false,\"s\":\"bar\"}}")), "strict parsing: d missing"); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_alloc | e_error | e_free; c = 0; + testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"d\":1.2e15,\"c\":\"R\",\"i\":1,\"s\":\"bar\"}}")), "strict parsing: f missing"); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_alloc | e_error | e_free; c = 0; + testOk(!(pch = dbChannelCreate("x.{\"strict\":{\"c\":\"R\",\"s\":\"bar\",\"f\":false,\"d\":1.2e15}}")), "strict parsing: i missing"); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + + /* NOCONV parsing: optional, no conversion */ + + /* Any one missing must leave the default intact */ + e = e_alloc | e_ok; c = 0; + testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"s\":\"bar\"}}")), "noconv parsing: c missing"); + testOk(checkValues(puser, 1, 0, 1.2e15, "bar", 4), "guards intact, values correct"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + e = e_close | e_free; c = 0; + if (pch) dbChannelDelete(pch); + testOk(!puser, "user part cleaned up"); + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + + e = e_any; + testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"d\":1.2e15,\"c\":\"R\"}}")), "noconv parsing: s missing"); + testOk(checkValues(puser, 1, 0, 1.2e15, "hello", 1), "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"f\":false,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: d missing"); + testOk(checkValues(puser, 1, 0, 1.234e5, "bar", 1), "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"i\":1,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: f missing"); + testOk(checkValues(puser, 1, 1, 1.2e15, "bar", 1), "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + testOk(!!(pch = dbChannelCreate("x.{\"noconv\":{\"f\":false,\"d\":1.2e15,\"s\":\"bar\",\"c\":\"R\"}}")), "noconv parsing: i missing"); + testOk(checkValues(puser, 12, 0, 1.2e15, "bar", 1), "guards intact, values correct"); + if (pch) dbChannelDelete(pch); + + /* Reject wrong types */ +#define WRONGTYPETEST(Var, Val, Typ) \ + e = e_alloc | e_error | e_free; c = 0; \ + testOk(!(pch = dbChannelCreate("x.{\"noconv\":{\""#Var"\":"#Val"}}")), "noconv parsing: wrong type "#Typ" for "#Var); \ + testOk(!puser, "user part cleaned up"); \ + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + + WRONGTYPETEST(i, 123.0, double); + WRONGTYPETEST(i, true, boolean); + WRONGTYPETEST(i, "1", string); + WRONGTYPETEST(f, "false", string); + WRONGTYPETEST(f, 0.0, double); + WRONGTYPETEST(f, 1, integer); + WRONGTYPETEST(d, "1.2", string); + WRONGTYPETEST(d, true, boolean); + WRONGTYPETEST(d, 123, integer); + WRONGTYPETEST(s, 1.23, double); + WRONGTYPETEST(s, true, boolean); + WRONGTYPETEST(s, 123, integer); + WRONGTYPETEST(c, 1.23, double); + WRONGTYPETEST(c, true, boolean); + WRONGTYPETEST(c, 2, integer); + + /* SLOPPY parsing: optional, with conversion */ + +#define CONVTESTGOOD(Var, Val, Typ, Ival, Fval, Dval, Sval, Cval) \ + e = e_alloc | e_ok; c = 0; \ + testOk(!!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), "sloppy parsing: "#Typ" (good) for "#Var); \ + testOk(checkValues(puser, Ival, Fval, Dval, Sval, Cval), "guards intact, values correct"); \ + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); \ + e = e_close | e_free; c = 0; \ + if (pch) dbChannelDelete(pch); \ + testOk(!puser, "user part cleaned up"); \ + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + +#define CONVTESTBAD(Var, Val, Typ) \ + e = e_alloc | e_error | e_free; c = 0; \ + testOk(!(pch = dbChannelCreate("x.{\"sloppy\":{\""#Var"\":"#Val"}}")), "sloppy parsing: "#Typ" (bad) for "#Var); \ + testOk(!puser, "user part cleaned up"); \ + if (!testOk(c == e, "all expected calls happened")) testDiag("expected %#x - called %#x", e, c); + + /* To integer */ + CONVTESTGOOD(i, "123e4", positive string, 123, 1, 1.234e5, "hello", 4); + CONVTESTGOOD(i, "-12345", negative string, -12345, 1, 1.234e5, "hello", 4); + CONVTESTBAD(i, "9234567890", out-of-range string); + CONVTESTBAD(i, ".4", invalid string); + CONVTESTGOOD(i, false, valid boolean, 0, 1, 1.234e5, "hello", 4); + CONVTESTGOOD(i, 3456.789, valid double, 3456, 1, 1.234e5, "hello", 4); + CONVTESTBAD(i, 34.7e14, out-of-range double); + + /* To boolean */ + CONVTESTGOOD(f, "false", valid string, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, "False", capital valid string, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, "0", 0 string, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, "15", 15 string, 12, 1, 1.234e5, "hello", 4); + CONVTESTBAD(f, ".4", invalid .4 string); + CONVTESTBAD(f, "Flase", misspelled invalid string); + CONVTESTGOOD(f, 0, zero integer, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, 12, positive integer, 12, 1, 1.234e5, "hello", 4); + CONVTESTGOOD(f, -1234, negative integer, 12, 1, 1.234e5, "hello", 4); + CONVTESTGOOD(f, 0.4, positive non-zero double, 12, 1, 1.234e5, "hello", 4); + CONVTESTGOOD(f, 0.0, zero double, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, -0.0, minus-zero double, 12, 0, 1.234e5, "hello", 4); + CONVTESTGOOD(f, -1.24e14, negative double, 12, 1, 1.234e5, "hello", 4); + + /* To double */ + CONVTESTGOOD(d, "123e4", positive double string, 12, 1, 1.23e6, "hello", 4); + CONVTESTGOOD(d, "-7.89e-14", negative double string, 12, 1, -7.89e-14, "hello", 4); + CONVTESTGOOD(d, "123", positive integer string, 12, 1, 123.0, "hello", 4); + CONVTESTGOOD(d, "-1234567", negative integer string, 12, 1, -1.234567e6, "hello", 4); + CONVTESTBAD(d, "1.67e407", out-of-range double string); + CONVTESTBAD(d, "blubb", invalid blubb string); + CONVTESTGOOD(d, 123, positive integer, 12, 1, 123.0, "hello", 4); + CONVTESTGOOD(d, -12345, negative integer, 12, 1, -1.2345e4, "hello", 4); + CONVTESTGOOD(d, true, true boolean, 12, 1, 1.0, "hello", 4); + CONVTESTGOOD(d, false, false boolean, 12, 1, 0.0, "hello", 4); + + /* To string */ + CONVTESTGOOD(s, 12345, positive integer, 12, 1, 1.234e5, "12345", 4); + CONVTESTGOOD(s, -1234567891, negative integer, 12, 1, 1.234e5, "-1234567891", 4); + CONVTESTGOOD(s, true, true boolean, 12, 1, 1.234e5, "true", 4); + CONVTESTGOOD(s, false, false boolean, 12, 1, 1.234e5, "false", 4); + CONVTESTGOOD(s, 123e4, small positive double, 12, 1, 1.234e5, "1230000", 4); + CONVTESTGOOD(s, -123e24, negative double, 12, 1, 1.234e5, "-1.23e+26", 4); + CONVTESTGOOD(s, -1.23456789123456789e26, large rounded negative double, 12, 1, 1.234e5, "-1.234567891235e+26", 4); + + /* To Enum */ + CONVTESTGOOD(c, 2, valid integer choice, 12, 1, 1.234e5, "hello", 2); + CONVTESTBAD(c, 3, invalid integer choice); + CONVTESTBAD(c, 3.2, double); + CONVTESTGOOD(c, "R", valid string choice, 12, 1, 1.234e5, "hello", 1); + CONVTESTBAD(c, "blubb", invalid string choice); + + dbFreeBase(pdbbase); + + return testDone(); +}