src.dualinventive.com/mtinfo/tcpserver/legacy/lib/include/di-util/cp3000.h

743 lines
26 KiB
C

/*
************************************************************************
**
** Copyright (c) 2010..2013 by
** Core|Vision B.V.
** Cereslaan 10b
** 5384 VT Heesch
** The Netherlands
**
** All Rights Reserved
**
************************************************************************
*/
/*
************************************************************************
**
** Project name: Dual Inventive: Utility Library
** Filename: cp3000.h
** Author: Jack Weeland
** Date: January 27, 2010
** File version: $Revision: 1.23 $
** $Date: 2014/01/23 15:57:28 $
**
************************************************************************
*/
/*
************************************************************************
**
** CP3000 high(er) level protocol handling
**
** High level functions:
** - Create a server socket, accept clients
** - Create a client socket
** Utility functions:
** - The I/O buffers
** - Checking the validity of a message (checksum)
** - Escape and unescape strings, etc
**
************************************************************************
*/
#ifndef __CP3000_H
#define __CP3000_H
#include <di-util/list.h>
#include <stddef.h>
#include <stdarg.h>
#include <sys/select.h> // for 'fd_set'
/*
** Definitions - tokenizer
*/
// Flags for 'token_definition_t.flags'
// - Do not send the status in a reply to the originator; must be set also
// when the handler sends a status reply itself (but _not_ when it only
// sends intermediate replies, leaving the status reply to the library)
// or when the reply is sent asynchronously.
// - The handler _cannot_ send additional information in the reply, only
// a status code. If handler needs to supply additional information, it
// _must_ define the CP3000_SELF_REPLY flag.
#define CP3000_DEFAULT_REPLY 0
#define CP3000_NO_REPLY 0x00000001
#define CP3000_SELF_REPLY (CP3000_NO_REPLY)
#define CP3000_ASYNC_REPLY (CP3000_NO_REPLY)
// - Match only the first part of a token; as the 'token_definition_t'
// array will be handled in order, a partial match must always follow
// the entries that must be matched exactly
#define CP3000_MATCH_PARTIAL 0x00000100
// - Handle tokens (data) externally; must be the last item in the list
// and the "cmd" _must_ be NULL
#define CP3000_MATCH_ANY 0x00000200
// Security clearance level; a CP3000 command is only executed when the
// security level of a command is less than or equal to the security
// level of a device, as set with 'cp3000_set_authorization()'
#define CP3000_CLEARANCE(level) ((level) << 16)
#define CP3000_CLEARANCE_MASK 0xFFFF0000
// - Special flag for the reply code, to be used in the token_definition_t
// array as:
// { CP3000_REPLY, reply_handler, CP3000_REPLY_TOKEN }
// It also inhibits cp3000_process_data() from sending a reply, as this
// would result in a bouncing of replies on replies
#define CP3000_REPLY_TOKEN (CP3000_MATCH_PARTIAL | CP3000_NO_REPLY)
#define CP3000_CMD_TOKEN (CP3000_MATCH_PARTIAL | CP3000_NO_REPLY)
// Special characters in CP3000 commands
#define CP3000_REPLY (TCPFMT_TOKEN_REPLY)
#define CP3000_CMD (TCPFMT_TOKEN_CMD)
#define CP3000_SEP (TCPFMT_TOKEN_SEP)
#define CP3000_CLIENT (TCPFMT_TOKEN_CLIENTSEP)
#define CP3000_CHKSUM (TCPFMT_TOKEN_CHECKSUM)
// Placeholder for 'token_info_t.datasz', most notably for the 'datasz'
// argument for 'cp3000_create_token()'
#define CP3000_CMDTOKEN (-1)
/*
** Definitions - device flags
*/
// Flags for the default CP3000 parser, set with 'cp3000_set_flags()'
// - Don't check the checksum in the message
#define CP3000_IGNORE_CHECKSUM 0x00010000
// - Return raw data in 'token->cmd'; the argument list will be empty
// No checksum will be calculated nor checked
#define CP3000_RAW_TOKENS 0x00020000
// - Return binary data in 'token->data'; the size of the data buffer
// is stored in 'token->data_len'; the argument list will be empty
// No checksum will be calculated nor checked
#define CP3000_BINARY 0x00040000
// Temporary flag while receiving replies: do not process the putback
// list (should be modified with 'cp3000_set_flag()' and 'cp3000_clear_flag()'
#define CP3000_DONT_PROCESS_PUTBACK 0x80000000
/*
** Definitions - data types
*/
// Structure to hold the internal state
typedef struct CP3000_DEVICE *cp3000_device_t;
// Public and private keys and security certificates
typedef struct CP3000_KEY *cp3000_key_t;
// CP3000 tokens in the message from the client
typedef struct TOKEN_INFO
{
union {
const char *cmd; // command (or reply), the first token
void *data; // generic, binary data; 'args' is not used
};
int datasz; // number of bytes in the buffer pointed to by 'data';
// not used for strings
lst_t args; // argument list
int client; // client identifier, or '-1' when missing or not used
int status; // '-1' for a command or the status code from the client
} *token_info_t;
// Handler definition, which is called with the arguments of the
// command and an application defined context
typedef int (*token_handler_t)(cp3000_device_t, token_info_t, void*);
// token definition
typedef struct TOKEN_DEFINITION
{
const char *cmd; // command or place holder for a reply (CP3000_REPLY below)
token_handler_t handler; // handler
void *param; // pointer to paramters for the handler
unsigned int flags; // flags as defined above
} token_definition_t;
/*
** Definitions - extended mode parser
*/
// Place-holder for default CP3000 command processing
#define CP3000_DEFAULT_PARSER NULL
// Callback functions for the command parser
//
// Functions to initialize and destroy an optional private data structure for the
// parser
// Parameters:
// - parent cp3000_device_t object
// - parser parameters; see below
// Remark:
// For the 'init' function, the pointer 'param' initially points to 'param' argument
// for 'cp3000_add_parser()'. The 'init' function can allocate its own data structure
// (thus overriding the original 'param', which should be NULL in that case to avoid
// confusion).
// The 'destroy' function must deallocate this data structure in this scenario.
// Note:
// These functions can be NULL when they are not needed.
typedef int (*cp3000_parser_initfcn_t)(cp3000_device_t, void**);
typedef int (*cp3000_parser_destroyfcn_t)(cp3000_device_t, void*);
// Match the data in the buffer to check if it is the format expected by the parser
// Parameters:
// - parent cp3000_device_t object
// - parser parameters
// - pointer to the data buffer (data to match)
// - number of bytes in the data buffer
// Returns:
// < 0 on error or data not for this parser
// 0 if there isn't enough data, but buffer could contain a valid command
// > 0 successful match; return value is the number of bytes that can be
// processed
// Note:
// The value returned by the string match function determines how many bytes
// will be consumed from the I/O buffers. The data in the I/O buffers therefore
// _must_ be processed by this parser.
typedef int (*cp3000_parser_strmatchfcn_t)(cp3000_device_t, void *param, const void *buffer, size_t n);
// Process data
// Parameters:
// - parent cp3000_device_t object
// - parser parameters
// - pointer to the data buffer (data to process)
// - number of bytes in the data buffer
// Returns:
// < 0 on error or data not for this parser
// >= 0 command processed succesfully
// Note:
// At this point, the data has already been removed from the I/O buffers, so
// it is too late to determine during processor that the data is not intended
// for this parser. This _must_ be correctly determined in the string match
// member function.
typedef int (*cp3000_parser_processfcn_t)(cp3000_device_t, void *param, const void *buffer, size_t n);
// The command parser definition simply contains the the callback functions.
typedef const struct TOKEN_PARSER
{
// parser name, for debugging
const char *name;
// callbacks
cp3000_parser_initfcn_t cp3000_parser_init;
cp3000_parser_destroyfcn_t cp3000_parser_destroy;
cp3000_parser_strmatchfcn_t cp3000_parser_strmatch;
cp3000_parser_processfcn_t cp3000_parser_process;
} *token_parser_t;
/*
** Initialization
*/
// Initialization, must be called before anything else at the start of
// the application.
// The multi-threaded version also initializes multi-threading specific
// stuff. The 'mt' function calls the normal 'cp3000_init()'.
// Returns -1 on failure and 0 on success
int cp3000_init();
int cp3000_init_mt();
// De-initialization
int cp3000_deinit();
int cp3000_deinit_mt();
/*
** Client and server
*/
// Create a device. The device must be initialized to be a server or
// client using one of the 'cp3000_init_xxx()' functions that follow
// in the next sections.
// Returns NULL on error
cp3000_device_t cp3000_create_device();
// Destruction
// - Closes socket(s) and delete all OpenSSL related objects
// - Destroys the cp3000_device_t object
// Parameters:
// cp3000_device_t object to close and destroy
// Returns:
// -1 on error
int cp3000_destroy_device(cp3000_device_t);
// Attach security certificates to a device to enable transport layer
// security using OpenSSL. This must be done _before_ the device is
// initialized as a server or client.
// Parameters:
// cp3000_device_t object
// See also the functions defined in 'cp3000-cert.h'
// Important note: the OpenSSL functions have the peculiarity that they
// destroy their keys after use; the 'cp3000_key_t' parameters are
// therefore copied.
int cp3000_make_secure(
cp3000_device_t,
cp3000_key_t public_cert, // signed public certificate
cp3000_key_t trust_cert, // certificate of the CA Server
cp3000_key_t private_key // private key
);
// Test if the connection is secure
int cp3000_is_secure(cp3000_device_t);
// Initialize the SSL context, connect the read and write BIO to TLS/SSL
// and do the SSL handshake. Also sets the peer's address for non-secure
// clients.
// Parameters:
// The cp3000_device_t object
// Returns:
// 0 on succes, -1 on error
int cp3000_init_connection(cp3000_device_t);
// Close connection or file associated with this CP3000 object.
// User objects, flags and security descriptors will be retained and can be reused.
// Parameters:
// The cp3000_device_t object
// Returns:
// 0 on succes, -1 on error
int cp3000_disconnect(cp3000_device_t);
/*
** Server side
*/
// Initialize server
// - Creates a server socket.
// Parameters:
// cp3000_device_t object
// port - port number to listen to (-1 to use default from config or /etc/services)
int cp3000_init_server(
cp3000_device_t,
int port
);
// Wait for a client and accept the client.
// After accepting the client, the function 'cp3000_init_connection()'
// must be called, usually after the 'fork(2)'.
// Parameters:
// cp3000_device_t object for the server
// Returns:
// cp3000_device_t for the client
// NULL on error
// Remarks:
// Initializes the SSL object from 'server'.
cp3000_device_t cp3000_accept_client(
cp3000_device_t server
);
/*
** Client side
*/
// Connect to a server
// The function 'cp3000_init_connection()' must be called after this function.
// Parameters:
// cp3000_device_t object
// server - hostname:port
// Returns:
// 0 on succes, -1 on error
int cp3000_connect_to_server(
cp3000_device_t,
const char *server
);
/*
** Files and sockets (file descriptor interface)
*/
// Attach a file descriptor to it.
// The function 'cp3000_init_connection()' must be called after this function.
// Used for all clients, also those of a Unix-socket so they can use the
// CP3000 tokenizer and iobuffers
// Returns 0 on success and -1 on error;
int cp3000_attach_socket(
cp3000_device_t,
int fd
);
// Attach a read and write file descriptor to it.
// Used when a server is started from 'inetd' or 'xinetd'
// The function 'cp3000_init_connection()' must be called after this function.
// Returns 0 on success and -1 on error;
// Remark: the file descriptors are _not_ closed when the cp3000_device_t object
// is destroyed.
int cp3000_attach_fd(
cp3000_device_t,
int rfd,
int wfd
);
// Open a file using 'open(2)' and attach if to the CP3000 object
int cp3000_open_file(
cp3000_device_t,
const char *path,
int open_flags
);
/*
** Additional information
*/
// Get port number; returns -1 on error
int cp3000_get_port(cp3000_device_t);
// Get name of the peer
const char *cp3000_get_peername(cp3000_device_t);
// Byte counts
unsigned long cp3000_get_recv_bytes(cp3000_device_t);
unsigned long cp3000_get_send_bytes(cp3000_device_t);
// Special flags; see the definitions above
// The 'cp3000_set_flags()' function _replaces_ the flags
int cp3000_set_flags(cp3000_device_t, unsigned int flags);
unsigned int cp3000_get_flags(cp3000_device_t);
// Set or clear individual flag(s)
int cp3000_set_flag(cp3000_device_t, unsigned int flags);
int cp3000_clear_flag(cp3000_device_t, unsigned int flags);
/*
** Exported functions - user data
*/
// These two functions allow for adding user data to a cp3000_device_t
// object. The 'set' function makes a copy of the user data, so it is
// allowed to add an automatic object.
// Parameters:
// cp3000_device_t object
// data - Optional initial data; may be NULL
// datasz - Size of the user data, or '0' to delete the current data
// Returns:
// Pointer to the allocated buffer.
// Remark:
// This function always allocates a buffer when 'datasz' is non-zero
// and it returns a pointer to this buffer. When 'data' is NULL, this
// buffer will be uninitialized, otherwise 'data' is copied into it.
void *cp3000_set_user_data(cp3000_device_t device, const void *data, size_t datasz);
void *cp3000_get_user_data(cp3000_device_t);
int cp3000_destroy_user_data(cp3000_device_t);
/*
** Exported functions - extended (token) parser
*/
// Clear all parsers and add new ones
// Parameters:
// cp3000_device_t object
// token_parser_t object (pointer to a 'struct TOKEN_PARSER')
// parameter(s) for the parser functions
// Returns:
// Negative value on error
// Remark:
// The members of the 'token_parser_t' object are copied to an internal structure,
// so the object passed to this function may be a local object (i.e. it doesn't
// have to be a global or static object).
int cp3000_clear_parsers(cp3000_device_t);
int cp3000_add_parser(cp3000_device_t, token_parser_t, void *param);
// Replace all existing parsers, if any, by this (single) parser
// Simply calls 'cp3000_clear_parsers()' followed by 'cp3000_add_parser()'.
int cp3000_set_parser(cp3000_device_t, token_parser_t, void *param);
// Put data back into the buffer for later processing.
// Parameters:
// cp3000_device_t object
// data
// number of bytes
// Returns:
// < 0 on error, number of bytes otherwise
// Notes:
// The putback data is in fact consumed and stored an a separate buffer. When
// calling 'cp3000_process_data()', the putback buffer will be consumed first.
// The putback list is used to store data while waiting for a specific reply.
int cp3000_parser_putback(cp3000_device_t, const void *data, size_t datasz);
/*
** Exported functions - token parser main routine and default CP3000 command processor
*/
// Set a devices authorization level of executed comands
int cp3000_set_authorization(cp3000_device_t, int level);
// Check authorization
// Returns boolean
int cp3000_check_authorization(cp3000_device_t, int level);
// Receive data from a CP3000 communication channel
// Parameters:
// cp3000_device_t object (the communication channel)
// tokens - array of tokens that are recognised _or_ NULL to use
// the (token) parsers attached to the device with 'cp3000_add_parser()'
// Returns:
// Negative value ('-1') on any error; there is no way to distinguish
// which command returned the error, but 'di_errno' will be set to
// the last error.
// Negative value when the peer closed the communication channel;
// 'di_errno' will be set to TCPERR_EOF in this case.
// Number of commands/replies handled otherwise.
// Remarks:
// This function can be used to handle CP3000 commands only (simple
// behaviour) or a mixture of CP3000 and foreign data formats (the
// new, extended behaviour).
// The following, using the extended usage, is equivalent to the old,
// simple usage:
// // extended
// cp3000_clear_parsers(device);
// cp3000_add_parser(device, CP3000_DEFAULT_PARSER, token_definitions);
// ...
// cp3000_process_data(device, NULL);
// instead of
// // simple
// cp3000_process_data(device, token_definitions);
// Remarks (CP3000 i.e. old style behaviour):
// - Processes the putback list, filled by 'cp3000_recv_reply()', first
// - Checks the I/O buffer before reading from the socket
// - For each command read from the socket it will
// - Check the input (checksum, authentification)
// - Tokenize the input
// - Call the associated handler
// - Reply to the peer (if needed for the command, unless CP3000_NO_REPLY
// is set in the flags for the token definition)
int cp3000_process_data(cp3000_device_t, const token_definition_t *token_ctx);
/*
** Exported functions - old-style tokenizer for manual parsing
** NB: these functions are called from 'cp3000_process_data()'
*/
// Check if data is available in the internal buffers; if there is,
// the caller _mustn't_ call 'select(2)' to wait for data on the
// socket, but consume the pending data fist
// Returns '0' if there is none or a positive number otherwise
int cp3000_data_pending(cp3000_device_t);
// Process a tokenized CP3000 command
int cp3000_process_token(cp3000_device_t, const token_definition_t*, token_info_t);
// Tokenize a string buffer; returns the tokens found or NULL
// in the case of an error
// Parameters:
// buffer - string to tokenize
// Returns:
// Token object; must be deleted by cp3000_destroy_tokens().
// Remark:
// Called from cp3000_process_data().
// To be used for CP3000 commands only.
token_info_t cp3000_tokenize(const char *buffer);
// Duplicate a token
// Parameters:
// tokens
// Returns:
// Token object; must be deleted by cp3000_destroy_tokens().
// Note:
// Token found in a 'cp3000_process_data()' cycle only have a limited
// lifetime. To store them for later use, they must be duplicated with
// this function.
token_info_t cp3000_duplicate_token(const token_info_t);
// Create an empty token or make a binary token
// Parameters:
// data
// number of bytes, or zero when 'data' is a string
// Returns:
// Token object; must be deleted by cp3000_destroy_tokens().
// Notes:
// Creates an empty token when 'datasz' is zero and 'data' is NULL.
// Duplicates the string pointed to by 'data' when 'datasz' is zero.
// Duplicates a binary data buffer of size 'datasz' otherwise.
token_info_t cp3000_create_token(const void *data, size_t datasz);
// Set arguments, status and client identifier in a token created with 'cp3000_create_token()'
int cp3000_token_add_arg(token_info_t tokens, const char *arg);
int cp3000_token_set_status(token_info_t tokens, int status);
int cp3000_token_set_client(token_info_t tokens, int client_id);
// Delete the token found by 'cp3000_tokenize()' or duplicated with 'cp3000_duplicate_token()'
int cp3000_destroy_token(token_info_t tokens);
// old name
#define cp3000_delete_token(t) cp3000_destroy_token(t)
/*
** Exported functions - CP3000 commands and replies
*/
// Send a command over the TCP channel.
// Parameters:
// cp3000_device_t object (the communication channel)
// fmt - a TCPFMT_xxx with the command and its parameters
// ... - arguments
// Returns:
// Negative value on error.
// Remarks:
// Caller must escape all parameters.
int cp3000_send_cmd(cp3000_device_t, const char *fmt, ...);
int cp3000_vsend_cmd(cp3000_device_t, const char *fmt, va_list ap);
// Send a reply; 'reply' is optional and may be NULL
// Parameters:
// cp3000_device_t object (the communication channel)
// status_code - two byte (0..255) status code as defined in <cp3000-errno.h>
// fmt - optional additional reply text format (may be NULL)
// Returns:
// Negative value on error.
int cp3000_send_reply(cp3000_device_t, int status_code, const char *fmt, ...);
int cp3000_vsend_reply(cp3000_device_t, int status_code, const char *fmt, va_list ap);
// Receive a reply
// Parameters:
// cp3000_device_t object (the communication channel)
// status code (pointer to)
// msg - text part of the reply (may be NULL)
// msg_sz - size of 'msg'
// Returns:
// Number of bytes in 'msg' or '-1' on error
// Remarks:
// Fills the putback list, which must be processed
int cp3000_recv_reply(cp3000_device_t device, int *status_code, char *msg, size_t msg_sz);
int cp3000_recv_reply_for_client(cp3000_device_t device, int client_id, int *status_code, char *msg, size_t msg_sz);
// Forward the tokens received
// Parameters:
// cp3000_device_t object (the communication channel)
// tokens to send
// Returns:
// Negative value on error.
int cp3000_forward_tokens(cp3000_device_t, token_info_t tokens);
// find the definition of the token returned by 'cp3000_tokenize()'
// in the list 'definitions' (the last entry in this list must have
// its 'cmd' field set to NULL)
const token_definition_t *cp3000_match_token(const token_definition_t *definitions, token_info_t token);
/*
** Checksum calculation and checking
*/
// calculate the checksum of a message
int cp3000_checksum(const char *buffer);
// check checksum in a message; return '0' when the checksum matches,
// '-1' when the message does not contain a checksum or a positive
// value when the checksum does not match.
int cp3000_match_checksum(const char *buffer);
/*
** Wait for data
*/
// Read data from a CP3000 communication channel, in three steps
// 1. Optionally, wait until data is availble using 'cp3000_select()'
// 2. Read it and store it in the internal buffer
// 3. Read strings from the internal buffer
// Wait until data is ready to be read, with optional time-out.
// Similar to 'select(2)' (which _could_ be called, for that matter),
// but also checks the internal buffers.
// OpenSSL safe, as 'select(2)' may not always work as intended.
// Parameters:
// - An array (with count) of cp3000_device_t's to check. The array
// is _changed_ and contains the devices with data available upon
// exit.
// - Optional time-out if not NULL; when the time-out is specified and
// set to 0.0 seconds, the function will return immediately
// Returns:
// < 0 on error
// 0 when the time-out was triggered
// - number of entries in the cp3000_device_t; these are the device(s)
// that have data available
int cp3000_select(cp3000_device_t*, int n_devices, struct timeval*);
// Check if a device is in the array upon returning from 'cp3000_wait_data()'
int cp3000_in_select(const cp3000_device_t, const cp3000_device_t *devices, int n_devices);
/*
** Raw data access
** It is recommended to use the 'tokenizer' functions above
*/
// Synchronise with the peer, as good as possible
// Returns:
// 1 on success
// 0 or < 0 on failure
int cp3000_sync(cp3000_device_t);
// Send data over a CP3000 communication channel
int cp3000_send_data(cp3000_device_t, const char *data, int len);
// Read data from the CP3000 channel into the internal buffer
// Returns:
// > 0: number of bytes read from the communication channel in
// this call
// 0: channel was closed
// < 0 on error
int cp3000_recv_data(cp3000_device_t);
// Read a string or binary data from the internal buffer
// Returns:
// 0 if there is not enough data, i.e. not a full strings worth
// > 0: lenght of the string returned in 'buffer'
// < 0 on error
// Remarks:
// The string reader will read upto the next new line and it returns the length
// of the string in the buffer. The binary reader will return 'n_bytes' or zero
// if the buffer does not contain enough data to satisfy the request.
int cp3000_recv_string(cp3000_device_t, char *buffer, int bufsz);
int cp3000_recv_bytes(cp3000_device_t, unsigned char *buffer, int n_bytes);
// Access to the file descriptors for low level access
int cp3000_get_rfd(cp3000_device_t);
int cp3000_get_wfd(cp3000_device_t);
// Wrappers for FD_SET and FD_ISSET; the parameter 'max' is used to determine
// the file descriptor with the greatest number (if fd > max then max := fd;)
int cp3000_fd_set(cp3000_device_t, fd_set*, int *max);
int cp3000_in_fd_set(cp3000_device_t, fd_set*);
/*
** Escape and unescape strings
*/
// escape quotes and other characters in a string and returns 'dest' or
// NULL when 'dest' is too small
// - the size of 'dest' is, worst case, four times as great as 'src'
const char *cp3000_escape(const char *src, char *dest, size_t dest_sz);
// escape binary data
const char *cp3000_nescape(const unsigned char *src, size_t src_sz, char *dest, size_t dest_sz);
// remove the escapes and returns 'dest' or NULL if 'dest' is too small
// - 'src' and 'dest' may overlap
// - 'dest' will never be longer than 'src'
const char *cp3000_unescape(const char *src, char *dest, size_t dest_sz);
/*
** Convert an array of bytes to a string with hexadecimal numbers and v.v.
*/
// escape an array of bytes (the 'dest' string will contain the hexa-
// decimal representation of each byte) and v.v.
// - the size of 'dest' must be (2*src_sz + 1) characters
// - always returns the number of characters is 'dest' for success
int cp3000_bin2hex(const unsigned char *src, size_t src_sz, char *dest);
// the reverse function may fail if the destination is not large enough
// (in which case this function returns '-1', however 'dest' will be
// filled up to 'dest_sz' bytes), otherwise it returns the number of
// bytes in 'dest'.
int cp3000_hex2bin(const char *src, unsigned char *dest, size_t dest_sz);
/*
** Convert a string to an enumeration value or set (bitfield)
*/
// for both functions, the input array 'members' must be in order, i.e.
// the first entry returns enumeration value '0' resp. set value 0x0001;
// NULL values can be used as place holders (they are never matched)
// convert a string to an enumeration; returning the first match
// return '-1' on error
int cp3000_str2enum(const char *str, const char **members, int n_members);
// convert a string to a set value (bitfield)
// 'str' contains zero or more comma separated values that are to be
// matched in 'members'
unsigned long cp3000_str2set(const char *str, const char **members, int n_members);
/*
** Errors
*/
// get error string for the supplied error code; if this is TCPERR_SOCKET,
// then the function will try to retrieve the last error from the C library
// or OpenSSL library
const char *cp3000_error_string(cp3000_device_t, int error_code);
#endif /* __CP3000_H */