diff options
| author | Oskari Timperi <oskari.timperi@iki.fi> | 2017-02-12 17:12:52 +0200 |
|---|---|---|
| committer | Oskari Timperi <oskari.timperi@iki.fi> | 2017-02-12 17:12:52 +0200 |
| commit | cd2faf64b2e995dee42e7e3911325d372c6604a1 (patch) | |
| tree | e0ab608cdd7005e759b570a5a4ec2031191bafb5 /tools | |
| parent | 54f78d73ec378fe1d62a696edeb2ddc5a59e2669 (diff) | |
| download | mqtt-cd2faf64b2e995dee42e7e3911325d372c6604a1.tar.gz mqtt-cd2faf64b2e995dee42e7e3911325d372c6604a1.zip | |
Add all the stuff
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/CMakeLists.txt | 7 | ||||
| -rw-r--r-- | tools/amalgamate.py | 90 | ||||
| -rw-r--r-- | tools/getopt.c | 358 | ||||
| -rw-r--r-- | tools/getopt.h | 175 | ||||
| -rw-r--r-- | tools/pub.c | 96 | ||||
| -rw-r--r-- | tools/sub.c | 103 |
6 files changed, 829 insertions, 0 deletions
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..1a736ab --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,7 @@ +ADD_LIBRARY(getopt OBJECT getopt.c) + +ADD_EXECUTABLE(pub pub.c $<TARGET_OBJECTS:getopt>) +TARGET_LINK_LIBRARIES(pub mqtt) + +ADD_EXECUTABLE(sub sub.c $<TARGET_OBJECTS:getopt>) +TARGET_LINK_LIBRARIES(sub mqtt) diff --git a/tools/amalgamate.py b/tools/amalgamate.py new file mode 100644 index 0000000..a807603 --- /dev/null +++ b/tools/amalgamate.py @@ -0,0 +1,90 @@ +import io +import os +import sys + +this_dir = os.path.dirname(os.path.abspath(__file__)) + +src_dir = os.path.join(this_dir, '..', 'src') + +sources = ( + 'queue.h', + 'log.h', + 'misc.c', + 'stringbuf.c', + 'stream.c', + 'socketstream.c', + 'stringstream.c', + 'stream_mqtt.c', + 'socket.c', + 'packet.c', + 'serialize.c', + 'deserialize.c', + 'client.c' +) + + +def is_header(filename): + return os.path.splitext(filename)[1] == '.h' + + +def get_header(src): + root, ext = os.path.splitext(src) + return root + '.h' + + +def read_file(filename): + def tounicode(s): + if sys.version_info[0] == 2: + return s.decode('utf-8') + else: + return s + with open(filename, 'r') as fp: + buf = io.StringIO() + for line in fp: + if line.startswith('#include "'): + if line[10:].startswith('mqtt.h'): + pass + else: + continue + buf.write(tounicode(line)) + return buf.getvalue() + + +def file_header(filename): + filename = os.path.basename(filename) + # how long lines we create + linelen = 72 + # how much space left after the necessary comment markup + chars = linelen - 4 + # how much padding in total for filename + padding = chars - len(filename) + padding_l = padding // 2 + padding_r = padding - padding_l + lines = ( + '', + '/*' + '*'*chars + '*/', + '/*' + ' '*padding_l + filename + ' '*padding_r + '*/', + '/*' + '*'*chars + '*/', + '\n', + ) + return '\n'.join(lines) + + +def write_file(output, srcfilename): + output.write(file_header(srcfilename)) + output.write(read_file(srcfilename)) + + +output_filename = sys.argv[1] + +with open(output_filename, 'w') as out: + for source in sources: + path = os.path.join(src_dir, source) + + if is_header(path): + write_file(out, path) + else: + header = get_header(path) + if os.path.isfile(header): + write_file(out, header) + write_file(out, path) diff --git a/tools/getopt.c b/tools/getopt.c new file mode 100644 index 0000000..5277ed0 --- /dev/null +++ b/tools/getopt.c @@ -0,0 +1,358 @@ +#include <assert.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "getopt.h" + +/* + * Standard getopt global variables. optreset starts as non-zero in order to + * trigger initialization behaviour. + */ +const char * optarg = NULL; +int optind = 1; +int opterr = 1; +int optreset = 1; + +/* + * Quasi-internal global variables -- these are used via GETOPT macros. + */ +const char * getopt_dummy = "(dummy)"; +int getopt_initialized = 0; + +/* + * Internal variables. + */ +static const char * cmdname = NULL; +static struct opt { + const char * os; + size_t olen; + int hasarg; +} * opts = NULL; +static size_t nopts; +static size_t opt_missing; +static size_t opt_default; +static size_t opt_found; +static const char * packedopts; +static char popt[3]; +static int atexit_registered = 0; + +/* Print a message. */ +#define PRINTMSG(...) do { \ + if (cmdname != NULL) \ + fprintf(stderr, "%s: ", cmdname); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ +} while (0) + +/* Print an error message and die. */ +#define DIE(...) do { \ + PRINTMSG(__VA_ARGS__); \ + abort(); \ +} while (0) + +/* Print a warning, if warnings are enabled. */ +#define WARN(...) do { \ + if (opterr == 0) \ + break; \ + if (opt_missing != opt_default) \ + break; \ + PRINTMSG(__VA_ARGS__); \ +} while (0) + +/* Free allocated options array. */ +static void +atexit_handler(void) +{ + + free(opts); + opts = NULL; +} + +/* Reset internal state. */ +static void +reset(int argc, char * const argv[]) +{ + const char * p; + + /* If we have arguments, stash argv[0] for error messages. */ + if (argc > 0) { + /* Find the basename, without leading directories. */ + for (p = cmdname = argv[0]; *p != '\0'; p++) { + if (*p == '/') + cmdname = p + 1; + } + } + + /* Discard any registered command-line options. */ + free(opts); + opts = NULL; + + /* Register atexit handler if we haven't done so already. */ + if (!atexit_registered) { + atexit(atexit_handler); + atexit_registered = 1; + } + + /* We will start scanning from the first option. */ + optind = 1; + + /* We're not in the middle of any packed options. */ + packedopts = NULL; + + /* We haven't found any option yet. */ + opt_found = (size_t)(-1); + + /* We're not initialized yet. */ + getopt_initialized = 0; + + /* Finished resetting state. */ + optreset = 0; +} + +/* Search for an option string. */ +static size_t +searchopt(const char * os) +{ + size_t i; + + /* Scan the array of options. */ + for (i = 0; i < nopts; i++) { + /* Is there an option in this slot? */ + if (opts[i].os == NULL) + continue; + + /* Does this match up to the length of the option string? */ + if (strncmp(opts[i].os, os, opts[i].olen)) + continue; + + /* Do we have <option>\0 or <option>= ? */ + if ((os[opts[i].olen] == '\0') || (os[opts[i].olen] == '=')) + return (i); + } + + /* Not found. */ + return (opt_default); +} + +const char * +getopt(int argc, char * const argv[]) +{ + const char * os = NULL; + const char * canonical_os = NULL; + + /* No argument yet. */ + optarg = NULL; + + /* Reset the getopt state if needed. */ + if (optreset) + reset(argc, argv); + + /* If not initialized, return dummy option. */ + if (!getopt_initialized) + return (GETOPT_DUMMY); + + /* If we've run out of arguments, we're done. */ + if (optind >= argc) + return (NULL); + + /* + * If we're not already in the middle of a packed single-character + * options, see if we should start. + */ + if ((packedopts == NULL) && (argv[optind][0] == '-') && + (argv[optind][1] != '-') && (argv[optind][1] != '\0')) { + /* We have one or more single-character options. */ + packedopts = &argv[optind][1]; + } + + /* If we're processing single-character options, fish one out. */ + if (packedopts != NULL) { + /* Construct the option string. */ + popt[0] = '-'; + popt[1] = *packedopts; + popt[2] = '\0'; + os = popt; + + /* We've done this character. */ + packedopts++; + + /* Are we done with this string? */ + if (*packedopts == '\0') { + packedopts = NULL; + optind++; + } + } + + /* If we don't have an option yet, do we have dash-dash? */ + if ((os == NULL) && (argv[optind][0] == '-') && + (argv[optind][1] == '-')) { + /* If this is not "--\0", it's an option. */ + if (argv[optind][2] != '\0') + os = argv[optind]; + + /* Either way, we want to eat the string. */ + optind++; + } + + /* If we have found nothing which looks like an option, we're done. */ + if (os == NULL) + return (NULL); + + /* Search for the potential option. */ + opt_found = searchopt(os); + + /* If the option is not registered, give up now. */ + if (opt_found == opt_default) { + WARN("unknown option: %s", os); + return (os); + } + + /* The canonical option string is the one registered. */ + canonical_os = opts[opt_found].os; + + /* Does the option take an argument? */ + if (opts[opt_found].hasarg) { + /* + * If we're processing packed single-character options, the + * rest of the string is the argument to this option. + */ + if (packedopts != NULL) { + optarg = packedopts; + packedopts = NULL; + optind++; + } + + /* + * If the option string is <option>=<value>, extract that + * value as the option argument. + */ + if (os[opts[opt_found].olen] == '=') + optarg = &os[opts[opt_found].olen + 1]; + + /* + * If we don't have an argument yet, take one from the + * remaining command line. + */ + if ((optarg == NULL) && (optind < argc)) + optarg = argv[optind++]; + + /* If we still have no option, declare it MIA. */ + if (optarg == NULL) { + WARN("option requires an argument: %s", + opts[opt_found].os); + opt_found = opt_missing; + } + } else { + /* If we have --foo=bar, something went wrong. */ + if (os[opts[opt_found].olen] == '=') { + WARN("option doesn't take an argument: %s", + opts[opt_found].os); + opt_found = opt_default; + } + } + + /* Return the canonical option string. */ + return (canonical_os); +} + +size_t +getopt_lookup(const char * os) +{ + + /* Can't reset here. */ + if (optreset) + DIE("Can't reset in the middle of getopt loop"); + + /* We should only be called after initialization is complete. */ + assert(getopt_initialized); + + /* GETOPT_DUMMY should never get passed back to us. */ + assert(os != GETOPT_DUMMY); + + /* + * Make sure the option passed back to us corresponds to the one we + * found earlier. + */ + assert((opt_found == opt_missing) || (opt_found == opt_default) || + ((opt_found < nopts) && (strcmp(os, opts[opt_found].os) == 0))); + + /* Return the option number we identified earlier. */ + return (opt_found); +} + +void +getopt_register_opt(const char * os, size_t ln, int hasarg) +{ + + /* Can't reset here. */ + if (optreset) + DIE("Can't reset in the middle of getopt loop"); + + /* We should only be called during initialization. */ + assert(!getopt_initialized); + + /* We should have space allocated for registering options. */ + assert(opts != NULL); + + /* We should not have registered an option here yet. */ + assert(opts[ln].os == NULL); + + /* Options should be "-X" or "--foo". */ + if ((os[0] != '-') || (os[1] == '\0') || + ((os[1] == '-') && (os[2] == '\0')) || + ((os[1] != '-') && (os[2] != '\0'))) + DIE("Not a valid command-line option: %s", os); + + /* Make sure we haven't already registered this option. */ + if (searchopt(os) != opt_default) + DIE("Command-line option registered twice: %s", os); + + /* Record option. */ + opts[ln].os = os; + opts[ln].olen = strlen(os); + opts[ln].hasarg = hasarg; +} + +void +getopt_register_missing(size_t ln) +{ + + /* Can't reset here. */ + if (optreset) + DIE("Can't reset in the middle of getopt loop"); + + /* We should only be called during initialization. */ + assert(!getopt_initialized); + + /* Record missing-argument value. */ + opt_missing = ln; +} + +void +getopt_setrange(size_t ln) +{ + size_t i; + + /* Can't reset here. */ + if (optreset) + DIE("Can't reset in the middle of getopt loop"); + + /* We should only be called during initialization. */ + assert(!getopt_initialized); + + /* Allocate space for options. */ + opts = malloc(ln * sizeof(struct opt)); + if ((ln > 0) && (opts == NULL)) + DIE("Failed to allocate memory in getopt"); + + /* Initialize options. */ + for (i = 0; i < ln; i++) + opts[i].os = NULL; + + /* Record the number of (potential) options. */ + nopts = ln; + + /* Record default missing-argument and no-such-option values. */ + opt_missing = opt_default = ln + 1; +} diff --git a/tools/getopt.h b/tools/getopt.h new file mode 100644 index 0000000..5ed3145 --- /dev/null +++ b/tools/getopt.h @@ -0,0 +1,175 @@ +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +#include <setjmp.h> +#include <stddef.h> + +/** + * This getopt implementation parses options of the following forms: + * -a -b -c foo (single-character options) + * -abc foo (packed single-character options) + * -abcfoo (packed single-character options and an argument) + * --foo bar (long option) + * --foo=bar (long option and argument separated by '=') + * + * It does not support abbreviated options (e.g., interpreting --foo as + * --foobar when there are no other --foo* options) since that misfeature + * results in breakage when new options are added. It also does not support + * options appearing after non-options (e.g., "cp foo bar -R") since that is + * a horrible GNU perversion. + */ + +/* Work around LLVM bug. */ +#ifdef __clang__ +#warning Working around bug in LLVM optimizer +#warning For more details see https://llvm.org/bugs/show_bug.cgi?id=27190 +#define DO_SETJMP _DO_SETJMP(__LINE__) +#define _DO_SETJMP(x) __DO_SETJMP(x) +#define __DO_SETJMP(x) \ + void * getopt_initloop = && getopt_initloop_ ## x; \ + getopt_initloop_ ## x: +#define DO_LONGJMP \ + goto *getopt_initloop +#else +#define DO_SETJMP \ + sigjmp_buf getopt_initloop; \ + if (!getopt_initialized) \ + sigsetjmp(getopt_initloop, 0) +#define DO_LONGJMP \ + siglongjmp(getopt_initloop, 1) +#endif + +/* Avoid namespace collisions with libc getopt. */ +#define getopt libcperciva_getopt +#define optarg libcperciva_optarg +#define optind libcperciva_optind +#define opterr libcperciva_opterr +#define optreset libcperciva_optreset + +/* Standard getopt global variables. */ +extern const char * optarg; +extern int optind, opterr, optreset; + +/* Dummy option string, equal to "(dummy)". */ +#define GETOPT_DUMMY getopt_dummy + +/** + * GETOPT(argc, argv): + * When called for the first time (or the first time after optreset is set to + * a nonzero value), return GETOPT_DUMMY, aka. "(dummy)". Thereafter, return + * the next option string and set optarg / optind appropriately; abort if not + * properly initialized when not being called for the first time. + */ +#define GETOPT(argc, argv) getopt(argc, argv) + +/** + * GETOPT_SWITCH(ch): + * Jump to the appropriate GETOPT_OPT, GETOPT_OPTARG, GETOPT_MISSING_ARG, or + * GETOPT_DEFAULT based on the option string ${ch}. When called for the first + * time, perform magic to index the options. + * + * GETOPT_SWITCH(ch) is equivalent to "switch (ch)" in a standard getopt loop. + */ +#define GETOPT_SWITCH(ch) \ + volatile size_t getopt_ln_min = __LINE__; \ + volatile size_t getopt_ln = getopt_ln_min - 1; \ + volatile int getopt_default_missing = 0; \ + DO_SETJMP; \ + switch (getopt_initialized ? getopt_lookup(ch) + getopt_ln_min : getopt_ln++) + +/** + * GETOPT_OPT(os): + * Jump to this point when the option string ${os} is passed to GETOPT_SWITCH. + * + * GETOPT_OPT("-x") is equivalent to "case 'x'" in a standard getopt loop + * which has an optstring containing "x". + */ +#define GETOPT_OPT(os) _GETOPT_OPT(os, __LINE__) +#define _GETOPT_OPT(os, ln) __GETOPT_OPT(os, ln) +#define __GETOPT_OPT(os, ln) \ + case ln: \ + if (getopt_initialized) \ + goto getopt_skip_ ## ln; \ + getopt_register_opt(os, ln - getopt_ln_min, 0); \ + DO_LONGJMP; \ + getopt_skip_ ## ln + +/** + * GETOPT_OPTARG(os): + * Jump to this point when the option string ${os} is passed to GETOPT_SWITCH, + * unless no argument is available, in which case jump to GETOPT_MISSING_ARG + * (if present) or GETOPT_DEFAULT (if not). + * + * GETOPT_OPTARG("-x") is equivalent to "case 'x'" in a standard getopt loop + * which has an optstring containing "x:". + */ +#define GETOPT_OPTARG(os) _GETOPT_OPTARG(os, __LINE__) +#define _GETOPT_OPTARG(os, ln) __GETOPT_OPTARG(os, ln) +#define __GETOPT_OPTARG(os, ln) \ + case ln: \ + if (getopt_initialized) \ + goto getopt_skip_ ## ln; \ + getopt_register_opt(os, ln - getopt_ln_min, 1); \ + DO_LONGJMP; \ + getopt_skip_ ## ln + +/** + * GETOPT_MISSING_ARG: + * Jump to this point if an option string specified in GETOPT_OPTARG is seen + * but no argument is available. + * + * GETOPT_MISSING_ARG is equivalent to "case ':'" in a standard getopt loop + * which has an optstring starting with ":". As such, it also has the effect + * of disabling warnings about invalid options, as if opterr had been zeroed. + */ +#define GETOPT_MISSING_ARG _GETOPT_MISSING_ARG(__LINE__) +#define _GETOPT_MISSING_ARG(ln) __GETOPT_MISSING_ARG(ln) +#define __GETOPT_MISSING_ARG(ln) \ + case ln: \ + if (getopt_initialized) \ + goto getopt_skip_ ## ln; \ + getopt_register_missing(ln - getopt_ln_min); \ + DO_LONGJMP; \ + getopt_skip_ ## ln + +/** + * GETOPT_DEFAULT: + * Jump to this point if an unrecognized option is seen or if an option + * specified in GETOPT_OPTARG is seen, no argument is available, and there is + * no GETOPT_MISSING_ARG label. + * + * GETOPT_DEFAULT is equivalent to "case '?'" in a standard getopt loop. + * + * NOTE: This MUST be present in the GETOPT_SWITCH statement, and MUST occur + * after all other GETOPT_* labels. + */ +#define GETOPT_DEFAULT _GETOPT_DEFAULT(__LINE__) +#define _GETOPT_DEFAULT(ln) __GETOPT_DEFAULT(ln) +#define __GETOPT_DEFAULT(ln) \ + goto getopt_skip_ ## ln; \ + case ln: \ + getopt_initialized = 1; \ + break; \ + default: \ + if (getopt_initialized) \ + goto getopt_skip_ ## ln; \ + if (!getopt_default_missing) { \ + getopt_setrange(ln - getopt_ln_min); \ + getopt_default_missing = 1; \ + } \ + DO_LONGJMP; \ + getopt_skip_ ## ln + +/* + * The back-end implementation. These should be considered internal + * interfaces and not used directly. + */ +const char * getopt(int, char * const []); +size_t getopt_lookup(const char *); +void getopt_register_opt(const char *, size_t, int); +void getopt_register_missing(size_t); +void getopt_setrange(size_t); +extern const char * getopt_dummy; +extern int getopt_initialized; + +#endif /* !_GETOPT_H_ */ diff --git a/tools/pub.c b/tools/pub.c new file mode 100644 index 0000000..9b5af82 --- /dev/null +++ b/tools/pub.c @@ -0,0 +1,96 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "mqtt.h" +#include "getopt.h" + +struct options +{ + int qos; + int retain; + const char *topic; + const char *message; +}; + +void onConnect(MqttClient *client, MqttConnectionStatus status, + int sessionPresent) +{ + struct options *options = (struct options *) MqttClientGetUserData(client); + (void) client; + printf("onConnect rv=%d sessionPresent=%d\n", status, sessionPresent); + MqttClientPublishCString(client, options->qos, options->retain, + options->topic, options->message); + if (options->qos == 0) + MqttClientDisconnect(client); +} + +void onPublish(MqttClient *client, int id) +{ + printf("onPublish id=%d\n", id); + MqttClientDisconnect(client); +} + +void usage(const char *prog) +{ + fprintf(stderr, "%s [--qos QOS] [--retain] [--topic TOPIC] [--message MESSAGE]\n", + prog); + exit(1); +} + +int main(int argc, char **argv) +{ + MqttClient *client; + const char *opt; + struct options options; + + options.qos = 0; + options.retain = 0; + options.topic = "my/topic"; + options.message = "hello, world!"; + + while ((opt = GETOPT(argc, argv)) != NULL) + { + GETOPT_SWITCH(opt) + { + GETOPT_OPTARG("--qos"): + options.qos = strtol(optarg, NULL, 10); + if (options.qos < 0 || options.qos > 2) + { + fprintf(stderr, "invalid qos: %s\n", optarg); + return 1; + } + break; + + GETOPT_OPT("--retain"): + options.retain = 1; + break; + + GETOPT_OPTARG("--topic"): + options.topic = optarg; + break; + + GETOPT_OPTARG("--message"): + options.message = optarg; + break; + + GETOPT_MISSING_ARG: + fprintf(stderr, "missing argument to: %s\n", opt); + + GETOPT_DEFAULT: + usage(argv[0]); + break; + } + } + + client = MqttClientNew(NULL, 1); + + MqttClientSetOnConnect(client, onConnect); + MqttClientSetOnPublish(client, onPublish); + MqttClientSetUserData(client, &options); + + MqttClientConnect(client, "test.mosquitto.org", 1883, 60); + + MqttClientRun(client); + + return 0; +} diff --git a/tools/sub.c b/tools/sub.c new file mode 100644 index 0000000..634c1e3 --- /dev/null +++ b/tools/sub.c @@ -0,0 +1,103 @@ +#include <stdlib.h> +#include <stdio.h> + +#include "mqtt.h" +#include "getopt.h" + +struct options +{ + int qos; + const char *topic; + int clean; + const char *client_id; +}; + +void onConnect(MqttClient *client, MqttConnectionStatus status, + int sessionPresent) +{ + struct options *options = (struct options *) MqttClientGetUserData(client); + (void) client; + printf("onConnect rv=%d sessionPresent=%d\n", status, sessionPresent); + MqttClientSubscribe(client, options->topic, options->qos); +} + +void onSubscribe(MqttClient *client, int id, MqttSubscriptionStatus status) +{ + (void) client; + printf("onSubscribe id=%d status=%d\n", id, status); +} + +void onMessage(MqttClient *client, const char *topic, const void *data, + size_t size) +{ + (void) client; + printf("onMessage topic=<%s> message=<%.*s>\n", topic, (int) size, + (char *) data); + // MqttClientUnsubscribe(client, topic); +} + +void usage(const char *prog) +{ + fprintf(stderr, "%s [--qos QOS] [--topic TOPIC] [--clean] [--id ID]\n", + prog); + exit(1); +} + +int main(int argc, char **argv) +{ + MqttClient *client; + const char *opt; + struct options options; + + options.qos = 0; + options.topic = "$SYS/broker/load/messages/#"; + options.clean = 1; + options.client_id = NULL; + + while ((opt = GETOPT(argc, argv)) != NULL) + { + GETOPT_SWITCH(opt) + { + GETOPT_OPTARG("--qos"): + options.qos = strtol(optarg, NULL, 10); + if (options.qos < 0 || options.qos > 2) + { + fprintf(stderr, "invalid qos: %s\n", optarg); + return 1; + } + break; + + GETOPT_OPTARG("--topic"): + options.topic = optarg; + break; + + GETOPT_OPT("--no-clean"): + options.clean = 0; + break; + + GETOPT_OPTARG("--id"): + options.client_id = optarg; + break; + + GETOPT_MISSING_ARG: + fprintf(stderr, "missing argument to: %s\n", opt); + + GETOPT_DEFAULT: + usage(argv[0]); + break; + } + } + + client = MqttClientNew(options.client_id, options.clean); + + MqttClientSetOnConnect(client, onConnect); + MqttClientSetOnSubscribe(client, onSubscribe); + MqttClientSetOnMessage(client, onMessage); + MqttClientSetUserData(client, &options); + + MqttClientConnect(client, "test.mosquitto.org", 1883, 60); + + MqttClientRun(client); + + return 0; +} |
