From f3c8131a0df4bd1d5cf39b886ba7c355efc2430c Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko Date: Thu, 6 Sep 2018 08:38:38 +0300 Subject: [PATCH 1/4] update travis config (#662) --- CI/.travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CI/.travis.yml b/CI/.travis.yml index 8e9289b1..f5fed7a6 100644 --- a/CI/.travis.yml +++ b/CI/.travis.yml @@ -12,9 +12,9 @@ matrix: - os: osx osx_image: xcode8.3 # OS X 10.12 - os: osx - osx_image: xcode9 # OS X 10.12 + osx_image: xcode9.4 # OS X 10.13 - os: osx - osx_image: xcode9.2 # OS X 10.12 + osx_image: xcode10 # OS X 10.13 - os: linux dist: trusty sudo: required From 6e3d8d671ac59e308c2ec83136890dc1af2edc65 Mon Sep 17 00:00:00 2001 From: Oleg Moiseenko Date: Thu, 6 Sep 2018 08:48:54 +0300 Subject: [PATCH 2/4] implement argtable in hf 14a apdu (#490) * added `hf 14a reader` to source and added functionality to exec empty commands * added `hf 14a raw` * added samples to command's help * added some help * added changelog * update to new argtable3 --- https://github.com/argtable/argtable3 * changed included getopt to `https://github.com/freebsd/freebsd/blob/master/include/getopt.h` (getopt from freebsd with simplified BSD license) --- CHANGELOG.md | 1 + client/Makefile | 2 + client/cliparser/README.md | 13 + client/cliparser/argtable3.c | 5005 ++++++++++++++++++++++++++++++++++ client/cliparser/argtable3.h | 305 +++ client/cliparser/cliparser.c | 167 ++ client/cliparser/cliparser.h | 32 + client/cmdhf14a.c | 267 +- client/obj/cliparser/.dummy | 0 9 files changed, 5619 insertions(+), 173 deletions(-) create mode 100644 client/cliparser/README.md create mode 100644 client/cliparser/argtable3.c create mode 100644 client/cliparser/argtable3.h create mode 100644 client/cliparser/cliparser.c create mode 100644 client/cliparser/cliparser.h create mode 100644 client/obj/cliparser/.dummy diff --git a/CHANGELOG.md b/CHANGELOG.md index da6463e9..ca13dc85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ This project uses the changelog in accordance with [keepchangelog](http://keepac - Changed `hf 14a raw` - works with LED's and some exchange logic (Merlok) - Changed TLV parser messages to more convenient (Merlok) - Rewritten Legic Prime reader (`hf legic reader`, `write` and `fill`) - it is using xcorrelation now (AntiCat) +- `hf 14a` commands works via argtable3 commandline parsing library (Merlok) ### Fixed - Changed start sequence in Qt mode (fix: short commands hangs main Qt thread) (Merlok) diff --git a/client/Makefile b/client/Makefile index c6ca1cf1..54a77a84 100644 --- a/client/Makefile +++ b/client/Makefile @@ -106,6 +106,8 @@ CMDSRCS = $(SRC_SMARTCARD) \ polarssl/bignum.c\ polarssl/rsa.c\ polarssl/sha1.c\ + cliparser/argtable3.c\ + cliparser/cliparser.c\ mfkey.c\ loclass/cipher.c \ loclass/cipherutils.c \ diff --git a/client/cliparser/README.md b/client/cliparser/README.md new file mode 100644 index 00000000..2b321946 --- /dev/null +++ b/client/cliparser/README.md @@ -0,0 +1,13 @@ +# Command line parser + +cliparser - librari for proxmark with command line parsing high level functions. + +## External libraries: + +### argtable + +Argtable3 is a single-file, ANSI C, command-line parsing library that parses GNU-style command-line options. + +You can download argtable3 from this repository https://github.com/argtable/argtable3 + +[argtable3 license](https://github.com/argtable/argtable3/blob/master/LICENSE) diff --git a/client/cliparser/argtable3.c b/client/cliparser/argtable3.c new file mode 100644 index 00000000..f70f68c7 --- /dev/null +++ b/client/cliparser/argtable3.c @@ -0,0 +1,5005 @@ +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include "argtable3.h" + +// On Windows isspace crashes app in case of using Unicode character set and string to be above ASCII +// so you have to use _istspace instead of space +#ifdef UNICODE +#include + #define ISSPACE _istspace +#else + #define ISSPACE isspace +#endif + +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 2013 Tom G. Huang + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#ifndef ARG_UTILS_H +#define ARG_UTILS_H + +#define ARG_ENABLE_TRACE 0 +#define ARG_ENABLE_LOG 1 + +#ifdef __cplusplus +extern "C" { +#endif + +enum +{ + EMINCOUNT = 1, + EMAXCOUNT, + EBADINT, + // The same name define EOVERFLOW in errno.h on windows platform +#ifdef __STDC_WANT_SECURE_LIB__ + EOVERFLOW_, +#else + EOVERFLOW, +#endif + EBADDOUBLE, + EBADDATE, + EREGNOMATCH +}; + + +#if defined(_MSC_VER) +#define ARG_TRACE(x) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + do { if (ARG_ENABLE_TRACE) dbg_printf x; } while (0) \ + __pragma(warning(pop)) + +#define ARG_LOG(x) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4127)) \ + do { if (ARG_ENABLE_LOG) dbg_printf x; } while (0) \ + __pragma(warning(pop)) +#else +#define ARG_TRACE(x) \ + do { if (ARG_ENABLE_TRACE) dbg_printf x; } while (0) + +#define ARG_LOG(x) \ + do { if (ARG_ENABLE_LOG) dbg_printf x; } while (0) +#endif + +extern void dbg_printf(const char *fmt, ...); + +#ifdef __cplusplus +} +#endif + +#endif + +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include + + +void dbg_printf(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} + +/* $NetBSD: getopt.h,v 1.4 2000/07/07 10:43:54 ad Exp $ */ +/* $FreeBSD$ */ + +/*- + * SPDX-License-Identifier: BSD-2-Clause-NetBSD + * + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _GETOPT_H_ +#define _GETOPT_H_ + +#include + +/* + * GNU-like getopt_long()/getopt_long_only() with 4.4BSD optreset extension. + * getopt() is declared here too for GNU programs. + */ +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +struct option { + /* name of long option */ + const char *name; + /* + * one of no_argument, required_argument, and optional_argument: + * whether option takes an argument + */ + int has_arg; + /* if not NULL, set *flag to val when option found */ + int *flag; + /* if flag not NULL, value to set *flag to; else return value */ + int val; +}; + +__BEGIN_DECLS +int getopt_long(int, char * const *, const char *, + const struct option *, int *); +int getopt_long_only(int, char * const *, const char *, + const struct option *, int *); +#ifndef _GETOPT_DECLARED +#define _GETOPT_DECLARED +int getopt(int, char * const [], const char *); + +extern char *optarg; /* getopt(3) external variables */ +extern int optind, opterr, optopt; +#endif +#ifndef _OPTRESET_DECLARED +#define _OPTRESET_DECLARED +extern int optreset; /* getopt(3) external variable */ +#endif +__END_DECLS + +#endif /* !_GETOPT_H_ */ +/* $Id: getopt_long.c,v 1.1 2009/10/16 19:50:28 rodney Exp rodney $ */ +/* $OpenBSD: getopt_long.c,v 1.23 2007/10/31 12:34:57 chl Exp $ */ +/* $NetBSD: getopt_long.c,v 1.15 2002/01/31 22:43:40 tv Exp $ */ + +/* + * Copyright (c) 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +// $Id: getopt_long.c,v 1.1 2009/10/16 19:50:28 rodney Exp rodney $" + +/*- + * Copyright (c) 2000 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Dieter Baron and Thomas Klausner. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#if 0 +#include +#endif +#include +#include +#include + + +#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */ + +#ifdef REPLACE_GETOPT +int opterr = 1; /* if error message should be printed */ +int optind = 1; /* index into parent argv vector */ +int optopt = '?'; /* character checked for validity */ +int optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ +#endif + +#define PRINT_ERROR ((opterr) && (*options != ':')) + +#define FLAG_PERMUTE 0x01 /* permute non-options to the end of argv */ +#define FLAG_ALLARGS 0x02 /* treat non-options as args to option "-1" */ +#define FLAG_LONGONLY 0x04 /* operate as getopt_long_only */ + +/* return values */ +#define BADCH (int)'?' +#define BADARG ((*options == ':') ? (int)':' : (int)'?') +#define INORDER (int)1 + +#define EMSG "" + +static int getopt_internal(int, char * const *, const char *, + const struct option *, int *, int); +static int parse_long_options(char * const *, const char *, + const struct option *, int *, int); +static int gcd(int, int); +static void permute_args(int, int, int, char * const *); + +static char *place = EMSG; /* option letter processing */ + +/* XXX: set optreset to 1 rather than these two */ +static int nonopt_start = -1; /* first non option argument (for permute) */ +static int nonopt_end = -1; /* first option after non options (for permute) */ + +/* Error messages */ +static const char recargchar[] = "option requires an argument -- %c"; +static const char recargstring[] = "option requires an argument -- %s"; +static const char ambig[] = "ambiguous option -- %.*s"; +static const char noarg[] = "option doesn't take an argument -- %.*s"; +static const char illoptchar[] = "unknown option -- %c"; +static const char illoptstring[] = "unknown option -- %s"; + + + +#ifdef _WIN32 + +/* Windows needs warnx(). We change the definition though: + * 1. (another) global is defined, opterrmsg, which holds the error message + * 2. errors are always printed out on stderr w/o the program name + * Note that opterrmsg always gets set no matter what opterr is set to. The + * error message will not be printed if opterr is 0 as usual. + */ + +#include +#include + +#define MAX_OPTER_MSG_SIZE 128 + +extern char opterrmsg[MAX_OPTER_MSG_SIZE]; +char opterrmsg[MAX_OPTER_MSG_SIZE]; /* buffer for the last error message */ + +static void warnx(const char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); + /* + Make sure opterrmsg is always zero-terminated despite the _vsnprintf() + implementation specifics and manually suppress the warning. + */ + memset(opterrmsg, 0, sizeof opterrmsg); + if (fmt != NULL) +#ifdef __STDC_WANT_SECURE_LIB__ + _vsnprintf_s(opterrmsg, MAX_OPTER_MSG_SIZE, sizeof(opterrmsg) - 1, fmt, ap); +#else + _vsnprintf(opterrmsg, sizeof(opterrmsg) - 1, fmt, ap); +#endif + va_end(ap); + +#pragma warning(suppress: 6053) + fprintf(stderr, "%s\n", opterrmsg); +} + +#else +#include +#endif /*_WIN32*/ + + +/* + * Compute the greatest common divisor of a and b. + */ +static int +gcd(int a, int b) +{ + int c; + + c = a % b; + while (c != 0) { + a = b; + b = c; + c = a % b; + } + + return (b); +} + +/* + * Exchange the block from nonopt_start to nonopt_end with the block + * from nonopt_end to opt_end (keeping the same order of arguments + * in each block). + */ +static void +permute_args(int panonopt_start, int panonopt_end, int opt_end, + char * const *nargv) +{ + int cstart, cyclelen, i, j, ncycle, nnonopts, nopts, pos; + char *swap; + + /* + * compute lengths of blocks and number and size of cycles + */ + nnonopts = panonopt_end - panonopt_start; + nopts = opt_end - panonopt_end; + ncycle = gcd(nnonopts, nopts); + cyclelen = (opt_end - panonopt_start) / ncycle; + + for (i = 0; i < ncycle; i++) { + cstart = panonopt_end+i; + pos = cstart; + for (j = 0; j < cyclelen; j++) { + if (pos >= panonopt_end) + pos -= nnonopts; + else + pos += nopts; + swap = nargv[pos]; + /* LINTED const cast */ + ((char **) nargv)[pos] = nargv[cstart]; + /* LINTED const cast */ + ((char **)nargv)[cstart] = swap; + } + } +} + +/* + * parse_long_options -- + * Parse long options in argc/argv argument vector. + * Returns -1 if short_too is set and the option does not match long_options. + */ +static int +parse_long_options(char * const *nargv, const char *options, + const struct option *long_options, int *idx, int short_too) +{ + char *current_argv, *has_equal; + size_t current_argv_len; + int i, match; + + current_argv = place; + match = -1; + + optind++; + + if ((has_equal = strchr(current_argv, '=')) != NULL) { + /* argument found (--option=arg) */ + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + /* find matching long option */ + if (strncmp(current_argv, long_options[i].name, + current_argv_len)) + continue; + + if (strlen(long_options[i].name) == current_argv_len) { + /* exact match */ + match = i; + break; + } + /* + * If this is a known short option, don't allow + * a partial match of a single character. + */ + if (short_too && current_argv_len == 1) + continue; + + if (match == -1) /* partial match */ + match = i; + else { + /* ambiguous abbreviation */ + if (PRINT_ERROR) + warnx(ambig, (int)current_argv_len, + current_argv); + optopt = 0; + return (BADCH); + } + } + if (match != -1) { /* option found */ + if (long_options[match].has_arg == no_argument + && has_equal) { + if (PRINT_ERROR) + warnx(noarg, (int)current_argv_len, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + return (BADARG); + } + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else if (long_options[match].has_arg == + required_argument) { + /* + * optional argument doesn't use next nargv + */ + optarg = nargv[optind++]; + } + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument; leading ':' indicates no error + * should be generated. + */ + if (PRINT_ERROR) + warnx(recargstring, + current_argv); + /* + * XXX: GNU sets optopt to val regardless of flag + */ + if (long_options[match].flag == NULL) + optopt = long_options[match].val; + else + optopt = 0; + --optind; + return (BADARG); + } + } else { /* unknown option */ + if (short_too) { + --optind; + return (-1); + } + if (PRINT_ERROR) + warnx(illoptstring, current_argv); + optopt = 0; + return (BADCH); + } + if (idx) + *idx = match; + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + return (0); + } else + return (long_options[match].val); +} + +/* + * getopt_internal -- + * Parse argc/argv argument vector. Called by user level routines. + */ +static int +getopt_internal(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx, int flags) +{ + char *oli; /* option letter list index */ + int optchar, short_too; + static int posixly_correct = -1; +#ifdef __STDC_WANT_SECURE_LIB__ + char* buffer = NULL; + size_t buffer_size = 0; + errno_t err = 0; +#endif + + if (options == NULL) + return (-1); + + /* + * Disable GNU extensions if POSIXLY_CORRECT is set or options + * string begins with a '+'. + */ + +#ifdef __STDC_WANT_SECURE_LIB__ + if (posixly_correct == -1) { + err = _dupenv_s(&buffer, &buffer_size, "POSIXLY_CORRECT") == 0; + posixly_correct = buffer != NULL; + if(buffer != NULL && err == 0) { + free(buffer); + } + } +#else + if (posixly_correct == -1) + posixly_correct = (getenv("POSIXLY_CORRECT") != NULL); +#endif + if (posixly_correct || *options == '+') + flags &= ~FLAG_PERMUTE; + else if (*options == '-') + flags |= FLAG_ALLARGS; + if (*options == '+' || *options == '-') + options++; + + /* + * XXX Some GNU programs (like cvs) set optind to 0 instead of + * XXX using optreset. Work around this braindamage. + */ + if (optind == 0) + optind = optreset = 1; + + optarg = NULL; + if (optreset) + nonopt_start = nonopt_end = -1; +start: + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc) { /* end of argument vector */ + place = EMSG; + if (nonopt_end != -1) { + /* do permutation, if we have to */ + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + else if (nonopt_start != -1) { + /* + * If we skipped non-options, set optind + * to the first of them. + */ + optind = nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + if (*(place = nargv[optind]) != '-' || + (place[1] == '\0' && strchr(options, '-') == NULL)) { + place = EMSG; /* found non-option */ + if (flags & FLAG_ALLARGS) { + /* + * GNU extension: + * return non-option as argument to option 1 + */ + optarg = nargv[optind++]; + return (INORDER); + } + if (!(flags & FLAG_PERMUTE)) { + /* + * If no permutation wanted, stop parsing + * at first non-option. + */ + return (-1); + } + /* do permutation */ + if (nonopt_start == -1) + nonopt_start = optind; + else if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + nonopt_start = optind - + (nonopt_end - nonopt_start); + nonopt_end = -1; + } + optind++; + /* process next argument */ + goto start; + } + if (nonopt_start != -1 && nonopt_end == -1) + nonopt_end = optind; + + /* + * If we have "-" do nothing, if "--" we are done. + */ + if (place[1] != '\0' && *++place == '-' && place[1] == '\0') { + optind++; + place = EMSG; + /* + * We found an option (--), so if we skipped + * non-options, we have to permute. + */ + if (nonopt_end != -1) { + permute_args(nonopt_start, nonopt_end, + optind, nargv); + optind -= nonopt_end - nonopt_start; + } + nonopt_start = nonopt_end = -1; + return (-1); + } + } + + /* + * Check long options if: + * 1) we were passed some + * 2) the arg is not just "-" + * 3) either the arg starts with -- we are getopt_long_only() + */ + if (long_options != NULL && place != nargv[optind] && + (*place == '-' || (flags & FLAG_LONGONLY))) { + short_too = 0; + if (*place == '-') + place++; /* --foo long option */ + else if (*place != ':' && strchr(options, *place) != NULL) + short_too = 1; /* could be short option too */ + + optchar = parse_long_options(nargv, options, long_options, + idx, short_too); + if (optchar != -1) { + place = EMSG; + return (optchar); + } + } + + if ((optchar = (int)*place++) == (int)':' || + (optchar == (int)'-' && *place != '\0') || + (oli = strchr(options, optchar)) == NULL) { + /* + * If the user specified "-" and '-' isn't listed in + * options, return -1 (non-option) as per POSIX. + * Otherwise, it is an unknown option character (or ':'). + */ + if (optchar == (int)'-' && *place == '\0') + return (-1); + if (!*place) + ++optind; + if (PRINT_ERROR) + warnx(illoptchar, optchar); + optopt = optchar; + return (BADCH); + } + if (long_options != NULL && optchar == 'W' && oli[1] == ';') { + /* -W long-option */ + if (*place) /* no space */ + /* NOTHING */; + else if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else /* white space */ + place = nargv[optind]; + optchar = parse_long_options(nargv, options, long_options, + idx, 0); + place = EMSG; + return (optchar); + } + if (*++oli != ':') { /* doesn't take argument */ + if (!*place) + ++optind; + } else { /* takes (optional) argument */ + optarg = NULL; + if (*place) /* no white space */ + optarg = place; + else if (oli[1] != ':') { /* arg not optional */ + if (++optind >= nargc) { /* no arg */ + place = EMSG; + if (PRINT_ERROR) + warnx(recargchar, optchar); + optopt = optchar; + return (BADARG); + } else + optarg = nargv[optind]; + } + place = EMSG; + ++optind; + } + /* dump back option letter */ + return (optchar); +} + +#ifdef REPLACE_GETOPT +/* + * getopt -- + * Parse argc/argv argument vector. + * + * [eventually this will replace the BSD getopt] + */ +int +getopt(int nargc, char * const *nargv, const char *options) +{ + + /* + * We don't pass FLAG_PERMUTE to getopt_internal() since + * the BSD getopt(3) (unlike GNU) has never done this. + * + * Furthermore, since many privileged programs call getopt() + * before dropping privileges it makes sense to keep things + * as simple (and bug-free) as possible. + */ + return (getopt_internal(nargc, nargv, options, NULL, NULL, 0)); +} +#endif /* REPLACE_GETOPT */ + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE)); +} + +/* + * getopt_long_only -- + * Parse argc/argv argument vector. + */ +int +getopt_long_only(int nargc, char * const *nargv, const char *options, + const struct option *long_options, int *idx) +{ + + return (getopt_internal(nargc, nargv, options, long_options, idx, + FLAG_PERMUTE|FLAG_LONGONLY)); +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include + +#include "argtable3.h" + + +char * arg_strptime(const char *buf, const char *fmt, struct tm *tm); + + +static void arg_date_resetfn(struct arg_date *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +static int arg_date_scanfn(struct arg_date *parent, const char *argval) +{ + int errorcode = 0; + + if (parent->count == parent->hdr.maxcount) + { + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* no argument value was given, leave parent->tmval[] unaltered but still count it */ + parent->count++; + } + else + { + const char *pend; + struct tm tm = parent->tmval[parent->count]; + + /* parse the given argument value, store result in parent->tmval[] */ + pend = arg_strptime(argval, parent->format, &tm); + if (pend && pend[0] == '\0') + parent->tmval[parent->count++] = tm; + else + errorcode = EBADDATE; + } + + ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static int arg_date_checkfn(struct arg_date *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + + ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static void arg_date_errorfn( + struct arg_date *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + case EBADDATE: + { + struct tm tm; + char buff[200]; + + fprintf(fp, "illegal timestamp format \"%s\"\n", argval); + memset(&tm, 0, sizeof(tm)); + arg_strptime("1999-12-31 23:59:59", "%F %H:%M:%S", &tm); + strftime(buff, sizeof(buff), parent->format, &tm); + printf("correct format is \"%s\"\n", buff); + break; + } + } +} + + +struct arg_date * arg_date0( + const char * shortopts, + const char * longopts, + const char * format, + const char *datatype, + const char *glossary) +{ + return arg_daten(shortopts, longopts, format, datatype, 0, 1, glossary); +} + + +struct arg_date * arg_date1( + const char * shortopts, + const char * longopts, + const char * format, + const char *datatype, + const char *glossary) +{ + return arg_daten(shortopts, longopts, format, datatype, 1, 1, glossary); +} + + +struct arg_date * arg_daten( + const char * shortopts, + const char * longopts, + const char * format, + const char *datatype, + int mincount, + int maxcount, + const char *glossary) +{ + size_t nbytes; + struct arg_date *result; + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + /* default time format is the national date format for the locale */ + if (!format) + format = "%x"; + + nbytes = sizeof(struct arg_date) /* storage for struct arg_date */ + + maxcount * sizeof(struct tm); /* storage for tmval[maxcount] array */ + + /* allocate storage for the arg_date struct + tmval[] array. */ + /* we use calloc because we want the tmval[] array zero filled. */ + result = (struct arg_date *)calloc(1, nbytes); + if (result) + { + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = datatype ? datatype : format; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_date_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_date_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_date_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_date_errorfn; + + /* store the tmval[maxcount] array immediately after the arg_date struct */ + result->tmval = (struct tm *)(result + 1); + + /* init the remaining arg_date member variables */ + result->count = 0; + result->format = format; + } + + ARG_TRACE(("arg_daten() returns %p\n", result)); + return result; +} + + +/*- + * Copyright (c) 1997, 1998, 2005, 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code was contributed to The NetBSD Foundation by Klaus Klein. + * Heavily optimised by David Laight + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include + +/* + * We do not implement alternate representations. However, we always + * check whether a given modifier is allowed for a certain conversion. + */ +#define ALT_E 0x01 +#define ALT_O 0x02 +#define LEGAL_ALT(x) { if (alt_format & ~(x)) return (0); } +#define TM_YEAR_BASE (1900) + +static int conv_num(const char * *, int *, int, int); + +static const char *day[7] = { + "Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", + "Friday", "Saturday" +}; + +static const char *abday[7] = { + "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" +}; + +static const char *mon[12] = { + "January", "February", "March", "April", "May", "June", "July", + "August", "September", "October", "November", "December" +}; + +static const char *abmon[12] = { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" +}; + +static const char *am_pm[2] = { + "AM", "PM" +}; + + +static int arg_strcasecmp(const char *s1, const char *s2) +{ + const unsigned char *us1 = (const unsigned char *)s1; + const unsigned char *us2 = (const unsigned char *)s2; + while (tolower(*us1) == tolower(*us2++)) + if (*us1++ == '\0') + return 0; + + return tolower(*us1) - tolower(*--us2); +} + + +static int arg_strncasecmp(const char *s1, const char *s2, size_t n) +{ + if (n != 0) + { + const unsigned char *us1 = (const unsigned char *)s1; + const unsigned char *us2 = (const unsigned char *)s2; + do + { + if (tolower(*us1) != tolower(*us2++)) + return tolower(*us1) - tolower(*--us2); + + if (*us1++ == '\0') + break; + } while (--n != 0); + } + + return 0; +} + + +char * arg_strptime(const char *buf, const char *fmt, struct tm *tm) +{ + char c; + const char *bp; + size_t len = 0; + int alt_format, i, split_year = 0; + + bp = buf; + + while ((c = *fmt) != '\0') { + /* Clear `alternate' modifier prior to new conversion. */ + alt_format = 0; + + /* Eat up white-space. */ + if (ISSPACE(c)) { + while (ISSPACE(*bp)) + bp++; + + fmt++; + continue; + } + + if ((c = *fmt++) != '%') + goto literal; + + +again: + switch (c = *fmt++) + { + case '%': /* "%%" is converted to "%". */ +literal: + if (c != *bp++) + return (0); + break; + + /* + * "Alternative" modifiers. Just set the appropriate flag + * and start over again. + */ + case 'E': /* "%E?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_E; + goto again; + + case 'O': /* "%O?" alternative conversion modifier. */ + LEGAL_ALT(0); + alt_format |= ALT_O; + goto again; + + /* + * "Complex" conversion rules, implemented through recursion. + */ + case 'c': /* Date and time, using the locale's format. */ + LEGAL_ALT(ALT_E); + bp = arg_strptime(bp, "%x %X", tm); + if (!bp) + return (0); + break; + + case 'D': /* The date as "%m/%d/%y". */ + LEGAL_ALT(0); + bp = arg_strptime(bp, "%m/%d/%y", tm); + if (!bp) + return (0); + break; + + case 'R': /* The time as "%H:%M". */ + LEGAL_ALT(0); + bp = arg_strptime(bp, "%H:%M", tm); + if (!bp) + return (0); + break; + + case 'r': /* The time in 12-hour clock representation. */ + LEGAL_ALT(0); + bp = arg_strptime(bp, "%I:%M:%S %p", tm); + if (!bp) + return (0); + break; + + case 'T': /* The time as "%H:%M:%S". */ + LEGAL_ALT(0); + bp = arg_strptime(bp, "%H:%M:%S", tm); + if (!bp) + return (0); + break; + + case 'X': /* The time, using the locale's format. */ + LEGAL_ALT(ALT_E); + bp = arg_strptime(bp, "%H:%M:%S", tm); + if (!bp) + return (0); + break; + + case 'x': /* The date, using the locale's format. */ + LEGAL_ALT(ALT_E); + bp = arg_strptime(bp, "%m/%d/%y", tm); + if (!bp) + return (0); + break; + + /* + * "Elementary" conversion rules. + */ + case 'A': /* The day of week, using the locale's form. */ + case 'a': + LEGAL_ALT(0); + for (i = 0; i < 7; i++) { + /* Full name. */ + len = strlen(day[i]); + if (arg_strncasecmp(day[i], bp, len) == 0) + break; + + /* Abbreviated name. */ + len = strlen(abday[i]); + if (arg_strncasecmp(abday[i], bp, len) == 0) + break; + } + + /* Nothing matched. */ + if (i == 7) + return (0); + + tm->tm_wday = i; + bp += len; + break; + + case 'B': /* The month, using the locale's form. */ + case 'b': + case 'h': + LEGAL_ALT(0); + for (i = 0; i < 12; i++) { + /* Full name. */ + len = strlen(mon[i]); + if (arg_strncasecmp(mon[i], bp, len) == 0) + break; + + /* Abbreviated name. */ + len = strlen(abmon[i]); + if (arg_strncasecmp(abmon[i], bp, len) == 0) + break; + } + + /* Nothing matched. */ + if (i == 12) + return (0); + + tm->tm_mon = i; + bp += len; + break; + + case 'C': /* The century number. */ + LEGAL_ALT(ALT_E); + if (!(conv_num(&bp, &i, 0, 99))) + return (0); + + if (split_year) { + tm->tm_year = (tm->tm_year % 100) + (i * 100); + } else { + tm->tm_year = i * 100; + split_year = 1; + } + break; + + case 'd': /* The day of month. */ + case 'e': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_mday, 1, 31))) + return (0); + break; + + case 'k': /* The hour (24-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'H': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_hour, 0, 23))) + return (0); + break; + + case 'l': /* The hour (12-hour clock representation). */ + LEGAL_ALT(0); + /* FALLTHROUGH */ + case 'I': + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_hour, 1, 12))) + return (0); + if (tm->tm_hour == 12) + tm->tm_hour = 0; + break; + + case 'j': /* The day of year. */ + LEGAL_ALT(0); + if (!(conv_num(&bp, &i, 1, 366))) + return (0); + tm->tm_yday = i - 1; + break; + + case 'M': /* The minute. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_min, 0, 59))) + return (0); + break; + + case 'm': /* The month. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &i, 1, 12))) + return (0); + tm->tm_mon = i - 1; + break; + + case 'p': /* The locale's equivalent of AM/PM. */ + LEGAL_ALT(0); + /* AM? */ + if (arg_strcasecmp(am_pm[0], bp) == 0) { + if (tm->tm_hour > 11) + return (0); + + bp += strlen(am_pm[0]); + break; + } + /* PM? */ + else if (arg_strcasecmp(am_pm[1], bp) == 0) { + if (tm->tm_hour > 11) + return (0); + + tm->tm_hour += 12; + bp += strlen(am_pm[1]); + break; + } + + /* Nothing matched. */ + return (0); + + case 'S': /* The seconds. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_sec, 0, 61))) + return (0); + break; + + case 'U': /* The week of year, beginning on sunday. */ + case 'W': /* The week of year, beginning on monday. */ + LEGAL_ALT(ALT_O); + /* + * XXX This is bogus, as we can not assume any valid + * information present in the tm structure at this + * point to calculate a real value, so just check the + * range for now. + */ + if (!(conv_num(&bp, &i, 0, 53))) + return (0); + break; + + case 'w': /* The day of week, beginning on sunday. */ + LEGAL_ALT(ALT_O); + if (!(conv_num(&bp, &tm->tm_wday, 0, 6))) + return (0); + break; + + case 'Y': /* The year. */ + LEGAL_ALT(ALT_E); + if (!(conv_num(&bp, &i, 0, 9999))) + return (0); + + tm->tm_year = i - TM_YEAR_BASE; + break; + + case 'y': /* The year within 100 years of the epoch. */ + LEGAL_ALT(ALT_E | ALT_O); + if (!(conv_num(&bp, &i, 0, 99))) + return (0); + + if (split_year) { + tm->tm_year = ((tm->tm_year / 100) * 100) + i; + break; + } + split_year = 1; + if (i <= 68) + tm->tm_year = i + 2000 - TM_YEAR_BASE; + else + tm->tm_year = i + 1900 - TM_YEAR_BASE; + break; + + /* + * Miscellaneous conversions. + */ + case 'n': /* Any kind of white-space. */ + case 't': + LEGAL_ALT(0); + while (ISSPACE(*bp)) + bp++; + break; + + + default: /* Unknown/unsupported conversion. */ + return (0); + } + + + } + + /* LINTED functional specification */ + return ((char *)bp); +} + + +static int conv_num(const char * *buf, int *dest, int llim, int ulim) +{ + int result = 0; + + /* The limit also determines the number of valid digits. */ + int rulim = ulim; + + if (**buf < '0' || **buf > '9') + return (0); + + do { + result *= 10; + result += *(*buf)++ - '0'; + rulim /= 10; + } while ((result * 10 <= ulim) && rulim && **buf >= '0' && **buf <= '9'); + + if (result < llim || result > ulim) + return (0); + + *dest = result; + return (1); +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "argtable3.h" + + +static void arg_dbl_resetfn(struct arg_dbl *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +static int arg_dbl_scanfn(struct arg_dbl *parent, const char *argval) +{ + int errorcode = 0; + + if (parent->count == parent->hdr.maxcount) + { + /* maximum number of arguments exceeded */ + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* a valid argument with no argument value was given. */ + /* This happens when an optional argument value was invoked. */ + /* leave parent argument value unaltered but still count the argument. */ + parent->count++; + } + else + { + double val; + char *end; + + /* extract double from argval into val */ + val = strtod(argval, &end); + + /* if success then store result in parent->dval[] array otherwise return error*/ + if (*end == 0) + parent->dval[parent->count++] = val; + else + errorcode = EBADDOUBLE; + } + + ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static int arg_dbl_checkfn(struct arg_dbl *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + + ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static void arg_dbl_errorfn( + struct arg_dbl *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + case EBADDOUBLE: + fprintf(fp, "invalid argument \"%s\" to option ", argval); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + } +} + + +struct arg_dbl * arg_dbl0( + const char * shortopts, + const char * longopts, + const char *datatype, + const char *glossary) +{ + return arg_dbln(shortopts, longopts, datatype, 0, 1, glossary); +} + + +struct arg_dbl * arg_dbl1( + const char * shortopts, + const char * longopts, + const char *datatype, + const char *glossary) +{ + return arg_dbln(shortopts, longopts, datatype, 1, 1, glossary); +} + + +struct arg_dbl * arg_dbln( + const char * shortopts, + const char * longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary) +{ + size_t nbytes; + struct arg_dbl *result; + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + nbytes = sizeof(struct arg_dbl) /* storage for struct arg_dbl */ + + (maxcount + 1) * sizeof(double); /* storage for dval[maxcount] array plus one extra for padding to memory boundary */ + + result = (struct arg_dbl *)malloc(nbytes); + if (result) + { + size_t addr; + size_t rem; + + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = datatype ? datatype : ""; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_dbl_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_dbl_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_dbl_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_dbl_errorfn; + + /* Store the dval[maxcount] array on the first double boundary that + * immediately follows the arg_dbl struct. We do the memory alignment + * purely for SPARC and Motorola systems. They require floats and + * doubles to be aligned on natural boundaries. + */ + addr = (size_t)(result + 1); + rem = addr % sizeof(double); + result->dval = (double *)(addr + sizeof(double) - rem); + ARG_TRACE(("addr=%p, dval=%p, sizeof(double)=%d rem=%d\n", addr, result->dval, (int)sizeof(double), (int)rem)); + + result->count = 0; + } + + ARG_TRACE(("arg_dbln() returns %p\n", result)); + return result; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "argtable3.h" + + +static void arg_end_resetfn(struct arg_end *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + +static void arg_end_errorfn( + void *parent, + FILE *fp, + int error, + const char *argval, + const char *progname) +{ + /* suppress unreferenced formal parameter warning */ + (void)parent; + + progname = progname ? progname : ""; + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(error) + { + case ARG_ELIMIT: + fputs("too many errors to display", fp); + break; + case ARG_EMALLOC: + fputs("insufficent memory", fp); + break; + case ARG_ENOMATCH: + fprintf(fp, "unexpected argument \"%s\"", argval); + break; + case ARG_EMISSARG: + fprintf(fp, "option \"%s\" requires an argument", argval); + break; + case ARG_ELONGOPT: + fprintf(fp, "invalid option \"%s\"", argval); + break; + default: + fprintf(fp, "invalid option \"-%c\"", error); + break; + } + + fputc('\n', fp); +} + + +struct arg_end * arg_end(int maxcount) +{ + size_t nbytes; + struct arg_end *result; + + nbytes = sizeof(struct arg_end) + + maxcount * sizeof(int) /* storage for int error[maxcount] array*/ + + maxcount * sizeof(void *) /* storage for void* parent[maxcount] array */ + + maxcount * sizeof(char *); /* storage for char* argval[maxcount] array */ + + result = (struct arg_end *)malloc(nbytes); + if (result) + { + /* init the arg_hdr struct */ + result->hdr.flag = ARG_TERMINATOR; + result->hdr.shortopts = NULL; + result->hdr.longopts = NULL; + result->hdr.datatype = NULL; + result->hdr.glossary = NULL; + result->hdr.mincount = 1; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_end_resetfn; + result->hdr.scanfn = NULL; + result->hdr.checkfn = NULL; + result->hdr.errorfn = (arg_errorfn *)arg_end_errorfn; + + /* store error[maxcount] array immediately after struct arg_end */ + result->error = (int *)(result + 1); + + /* store parent[maxcount] array immediately after error[] array */ + result->parent = (void * *)(result->error + maxcount ); + + /* store argval[maxcount] array immediately after parent[] array */ + result->argval = (const char * *)(result->parent + maxcount ); + } + + ARG_TRACE(("arg_end(%d) returns %p\n", maxcount, result)); + return result; +} + + +void arg_print_errors(FILE * fp, struct arg_end * end, const char * progname) +{ + int i; + ARG_TRACE(("arg_errors()\n")); + for (i = 0; i < end->count; i++) + { + struct arg_hdr *errorparent = (struct arg_hdr *)(end->parent[i]); + if (errorparent->errorfn) + errorparent->errorfn(end->parent[i], + fp, + end->error[i], + end->argval[i], + progname); + } +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include + +#include "argtable3.h" + +#ifdef WIN32 +# define FILESEPARATOR1 '\\' +# define FILESEPARATOR2 '/' +#else +# define FILESEPARATOR1 '/' +# define FILESEPARATOR2 '/' +#endif + + +static void arg_file_resetfn(struct arg_file *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +/* Returns ptr to the base filename within *filename */ +static const char * arg_basename(const char *filename) +{ + const char *result = NULL, *result1, *result2; + + /* Find the last occurrence of eother file separator character. */ + /* Two alternative file separator chars are supported as legal */ + /* file separators but not both together in the same filename. */ + result1 = (filename ? strrchr(filename, FILESEPARATOR1) : NULL); + result2 = (filename ? strrchr(filename, FILESEPARATOR2) : NULL); + + if (result2) + result = result2 + 1; /* using FILESEPARATOR2 (the alternative file separator) */ + + if (result1) + result = result1 + 1; /* using FILESEPARATOR1 (the preferred file separator) */ + + if (!result) + result = filename; /* neither file separator was found so basename is the whole filename */ + + /* special cases of "." and ".." are not considered basenames */ + if (result && ( strcmp(".", result) == 0 || strcmp("..", result) == 0 )) + result = filename + strlen(filename); + + return result; +} + + +/* Returns ptr to the file extension within *basename */ +static const char * arg_extension(const char *basename) +{ + /* find the last occurrence of '.' in basename */ + const char *result = (basename ? strrchr(basename, '.') : NULL); + + /* if no '.' was found then return pointer to end of basename */ + if (basename && !result) + result = basename + strlen(basename); + + /* special case: basenames with a single leading dot (eg ".foo") are not considered as true extensions */ + if (basename && result == basename) + result = basename + strlen(basename); + + /* special case: empty extensions (eg "foo.","foo..") are not considered as true extensions */ + if (basename && result && result[1] == '\0') + result = basename + strlen(basename); + + return result; +} + + +static int arg_file_scanfn(struct arg_file *parent, const char *argval) +{ + int errorcode = 0; + + if (parent->count == parent->hdr.maxcount) + { + /* maximum number of arguments exceeded */ + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* a valid argument with no argument value was given. */ + /* This happens when an optional argument value was invoked. */ + /* leave parent arguiment value unaltered but still count the argument. */ + parent->count++; + } + else + { + parent->filename[parent->count] = argval; + parent->basename[parent->count] = arg_basename(argval); + parent->extension[parent->count] = + arg_extension(parent->basename[parent->count]); /* only seek extensions within the basename (not the file path)*/ + parent->count++; + } + + ARG_TRACE(("%s4:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static int arg_file_checkfn(struct arg_file *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + + ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static void arg_file_errorfn( + struct arg_file *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + default: + fprintf(fp, "unknown error at \"%s\"\n", argval); + } +} + + +struct arg_file * arg_file0( + const char * shortopts, + const char * longopts, + const char *datatype, + const char *glossary) +{ + return arg_filen(shortopts, longopts, datatype, 0, 1, glossary); +} + + +struct arg_file * arg_file1( + const char * shortopts, + const char * longopts, + const char *datatype, + const char *glossary) +{ + return arg_filen(shortopts, longopts, datatype, 1, 1, glossary); +} + + +struct arg_file * arg_filen( + const char * shortopts, + const char * longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary) +{ + size_t nbytes; + struct arg_file *result; + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + nbytes = sizeof(struct arg_file) /* storage for struct arg_file */ + + sizeof(char *) * maxcount /* storage for filename[maxcount] array */ + + sizeof(char *) * maxcount /* storage for basename[maxcount] array */ + + sizeof(char *) * maxcount; /* storage for extension[maxcount] array */ + + result = (struct arg_file *)malloc(nbytes); + if (result) + { + int i; + + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.glossary = glossary; + result->hdr.datatype = datatype ? datatype : ""; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_file_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_file_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_file_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_file_errorfn; + + /* store the filename,basename,extension arrays immediately after the arg_file struct */ + result->filename = (const char * *)(result + 1); + result->basename = result->filename + maxcount; + result->extension = result->basename + maxcount; + result->count = 0; + + /* foolproof the string pointers by initialising them with empty strings */ + for (i = 0; i < maxcount; i++) + { + result->filename[i] = ""; + result->basename[i] = ""; + result->extension[i] = ""; + } + } + + ARG_TRACE(("arg_filen() returns %p\n", result)); + return result; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include +#include + +#include "argtable3.h" + + +static void arg_int_resetfn(struct arg_int *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +/* strtol0x() is like strtol() except that the numeric string is */ +/* expected to be prefixed by "0X" where X is a user supplied char. */ +/* The string may optionally be prefixed by white space and + or - */ +/* as in +0X123 or -0X123. */ +/* Once the prefix has been scanned, the remainder of the numeric */ +/* string is converted using strtol() with the given base. */ +/* eg: to parse hex str="-0X12324", specify X='X' and base=16. */ +/* eg: to parse oct str="+0o12324", specify X='O' and base=8. */ +/* eg: to parse bin str="-0B01010", specify X='B' and base=2. */ +/* Failure of conversion is indicated by result where *endptr==str. */ +static long int strtol0X(const char * str, + const char * *endptr, + char X, + int base) +{ + long int val; /* stores result */ + int s = 1; /* sign is +1 or -1 */ + const char *ptr = str; /* ptr to current position in str */ + + /* skip leading whitespace */ + while (ISSPACE(*ptr)) + ptr++; + /* printf("1) %s\n",ptr); */ + + /* scan optional sign character */ + switch (*ptr) + { + case '+': + ptr++; + s = 1; + break; + case '-': + ptr++; + s = -1; + break; + default: + s = 1; + break; + } + /* printf("2) %s\n",ptr); */ + + /* '0X' prefix */ + if ((*ptr++) != '0') + { + /* printf("failed to detect '0'\n"); */ + *endptr = str; + return 0; + } + /* printf("3) %s\n",ptr); */ + if (toupper(*ptr++) != toupper(X)) + { + /* printf("failed to detect '%c'\n",X); */ + *endptr = str; + return 0; + } + /* printf("4) %s\n",ptr); */ + + /* attempt conversion on remainder of string using strtol() */ + val = strtol(ptr, (char * *)endptr, base); + if (*endptr == ptr) + { + /* conversion failed */ + *endptr = str; + return 0; + } + + /* success */ + return s * val; +} + + +/* Returns 1 if str matches suffix (case insensitive). */ +/* Str may contain trailing whitespace, but nothing else. */ +static int detectsuffix(const char *str, const char *suffix) +{ + /* scan pairwise through strings until mismatch detected */ + while( toupper(*str) == toupper(*suffix) ) + { + /* printf("'%c' '%c'\n", *str, *suffix); */ + + /* return 1 (success) if match persists until the string terminator */ + if (*str == '\0') + return 1; + + /* next chars */ + str++; + suffix++; + } + /* printf("'%c' '%c' mismatch\n", *str, *suffix); */ + + /* return 0 (fail) if the matching did not consume the entire suffix */ + if (*suffix != 0) + return 0; /* failed to consume entire suffix */ + + /* skip any remaining whitespace in str */ + while (ISSPACE(*str)) + str++; + + /* return 1 (success) if we have reached end of str else return 0 (fail) */ + return (*str == '\0') ? 1 : 0; +} + + +static int arg_int_scanfn(struct arg_int *parent, const char *argval) +{ + int errorcode = 0; + + if (parent->count == parent->hdr.maxcount) + { + /* maximum number of arguments exceeded */ + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* a valid argument with no argument value was given. */ + /* This happens when an optional argument value was invoked. */ + /* leave parent arguiment value unaltered but still count the argument. */ + parent->count++; + } + else + { + long int val; + const char *end; + + /* attempt to extract hex integer (eg: +0x123) from argval into val conversion */ + val = strtol0X(argval, &end, 'X', 16); + if (end == argval) + { + /* hex failed, attempt octal conversion (eg +0o123) */ + val = strtol0X(argval, &end, 'O', 8); + if (end == argval) + { + /* octal failed, attempt binary conversion (eg +0B101) */ + val = strtol0X(argval, &end, 'B', 2); + if (end == argval) + { + /* binary failed, attempt decimal conversion with no prefix (eg 1234) */ + val = strtol(argval, (char * *)&end, 10); + if (end == argval) + { + /* all supported number formats failed */ + return EBADINT; + } + } + } + } + + /* Safety check for integer overflow. WARNING: this check */ + /* achieves nothing on machines where size(int)==size(long). */ + if ( val > INT_MAX || val < INT_MIN ) +#ifdef __STDC_WANT_SECURE_LIB__ + errorcode = EOVERFLOW_; +#else + errorcode = EOVERFLOW; +#endif + + /* Detect any suffixes (KB,MB,GB) and multiply argument value appropriately. */ + /* We need to be mindful of integer overflows when using such big numbers. */ + if (detectsuffix(end, "KB")) /* kilobytes */ + { + if ( val > (INT_MAX / 1024) || val < (INT_MIN / 1024) ) +#ifdef __STDC_WANT_SECURE_LIB__ + errorcode = EOVERFLOW_; /* Overflow would occur if we proceed */ +#else + errorcode = EOVERFLOW; /* Overflow would occur if we proceed */ +#endif + else + val *= 1024; /* 1KB = 1024 */ + } + else if (detectsuffix(end, "MB")) /* megabytes */ + { + if ( val > (INT_MAX / 1048576) || val < (INT_MIN / 1048576) ) +#ifdef __STDC_WANT_SECURE_LIB__ + errorcode = EOVERFLOW_; /* Overflow would occur if we proceed */ +#else + errorcode = EOVERFLOW; /* Overflow would occur if we proceed */ +#endif + else + val *= 1048576; /* 1MB = 1024*1024 */ + } + else if (detectsuffix(end, "GB")) /* gigabytes */ + { + if ( val > (INT_MAX / 1073741824) || val < (INT_MIN / 1073741824) ) +#ifdef __STDC_WANT_SECURE_LIB__ + errorcode = EOVERFLOW_; /* Overflow would occur if we proceed */ +#else + errorcode = EOVERFLOW; /* Overflow would occur if we proceed */ +#endif + else + val *= 1073741824; /* 1GB = 1024*1024*1024 */ + } + else if (!detectsuffix(end, "")) + errorcode = EBADINT; /* invalid suffix detected */ + + /* if success then store result in parent->ival[] array */ + if (errorcode == 0) + parent->ival[parent->count++] = val; + } + + /* printf("%s:scanfn(%p,%p) returns %d\n",__FILE__,parent,argval,errorcode); */ + return errorcode; +} + + +static int arg_int_checkfn(struct arg_int *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ + return errorcode; +} + + +static void arg_int_errorfn( + struct arg_int *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + case EBADINT: + fprintf(fp, "invalid argument \"%s\" to option ", argval); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + +#ifdef __STDC_WANT_SECURE_LIB__ + case EOVERFLOW_: +#else + case EOVERFLOW: +#endif + fputs("integer overflow at option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, " "); + fprintf(fp, "(%s is too large)\n", argval); + break; + } +} + + +struct arg_int * arg_int0( + const char *shortopts, + const char *longopts, + const char *datatype, + const char *glossary) +{ + return arg_intn(shortopts, longopts, datatype, 0, 1, glossary); +} + + +struct arg_int * arg_int1( + const char *shortopts, + const char *longopts, + const char *datatype, + const char *glossary) +{ + return arg_intn(shortopts, longopts, datatype, 1, 1, glossary); +} + + +struct arg_int * arg_intn( + const char *shortopts, + const char *longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary) +{ + size_t nbytes; + struct arg_int *result; + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + nbytes = sizeof(struct arg_int) /* storage for struct arg_int */ + + maxcount * sizeof(int); /* storage for ival[maxcount] array */ + + result = (struct arg_int *)malloc(nbytes); + if (result) + { + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = datatype ? datatype : ""; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_int_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_int_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_int_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_int_errorfn; + + /* store the ival[maxcount] array immediately after the arg_int struct */ + result->ival = (int *)(result + 1); + result->count = 0; + } + + ARG_TRACE(("arg_intn() returns %p\n", result)); + return result; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "argtable3.h" + + +static void arg_lit_resetfn(struct arg_lit *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +static int arg_lit_scanfn(struct arg_lit *parent, const char *argval) +{ + int errorcode = 0; + if (parent->count < parent->hdr.maxcount ) + parent->count++; + else + errorcode = EMAXCOUNT; + + ARG_TRACE(("%s:scanfn(%p,%s) returns %d\n", __FILE__, parent, argval, + errorcode)); + return errorcode; +} + + +static int arg_lit_checkfn(struct arg_lit *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static void arg_lit_errorfn( + struct arg_lit *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + switch(errorcode) + { + case EMINCOUNT: + fprintf(fp, "%s: missing option ", progname); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + fprintf(fp, "\n"); + break; + + case EMAXCOUNT: + fprintf(fp, "%s: extraneous option ", progname); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + } + + ARG_TRACE(("%s:errorfn(%p, %p, %d, %s, %s)\n", __FILE__, parent, fp, + errorcode, argval, progname)); +} + + +struct arg_lit * arg_lit0( + const char * shortopts, + const char * longopts, + const char * glossary) +{ + return arg_litn(shortopts, longopts, 0, 1, glossary); +} + + +struct arg_lit * arg_lit1( + const char *shortopts, + const char *longopts, + const char *glossary) +{ + return arg_litn(shortopts, longopts, 1, 1, glossary); +} + + +struct arg_lit * arg_litn( + const char *shortopts, + const char *longopts, + int mincount, + int maxcount, + const char *glossary) +{ + struct arg_lit *result; + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + result = (struct arg_lit *)malloc(sizeof(struct arg_lit)); + if (result) + { + /* init the arg_hdr struct */ + result->hdr.flag = 0; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = NULL; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_lit_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_lit_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_lit_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_lit_errorfn; + + /* init local variables */ + result->count = 0; + } + + ARG_TRACE(("arg_litn() returns %p\n", result)); + return result; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "argtable3.h" + +struct arg_rem *arg_rem(const char *datatype, const char *glossary) +{ + struct arg_rem *result = (struct arg_rem *)malloc(sizeof(struct arg_rem)); + if (result) + { + result->hdr.flag = 0; + result->hdr.shortopts = NULL; + result->hdr.longopts = NULL; + result->hdr.datatype = datatype; + result->hdr.glossary = glossary; + result->hdr.mincount = 1; + result->hdr.maxcount = 1; + result->hdr.parent = result; + result->hdr.resetfn = NULL; + result->hdr.scanfn = NULL; + result->hdr.checkfn = NULL; + result->hdr.errorfn = NULL; + } + + ARG_TRACE(("arg_rem() returns %p\n", result)); + return result; +} + +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include + +#include "argtable3.h" + + +#ifndef _TREX_H_ +#define _TREX_H_ +/*************************************************************** + T-Rex a tiny regular expression library + + Copyright (C) 2003-2006 Alberto Demichelis + + This software is provided 'as-is', without any express + or implied warranty. In no event will the authors be held + liable for any damages arising from the use of this software. + + Permission is granted to anyone to use this software for + any purpose, including commercial applications, and to alter + it and redistribute it freely, subject to the following restrictions: + + 1. The origin of this software must not be misrepresented; + you must not claim that you wrote the original software. + If you use this software in a product, an acknowledgment + in the product documentation would be appreciated but + is not required. + + 2. Altered source versions must be plainly marked as such, + and must not be misrepresented as being the original software. + + 3. This notice may not be removed or altered from any + source distribution. + +****************************************************************/ + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _UNICODE +#define TRexChar unsigned short +#define MAX_CHAR 0xFFFF +#define _TREXC(c) L##c +#define trex_strlen wcslen +#define trex_printf wprintf +#else +#define TRexChar char +#define MAX_CHAR 0xFF +#define _TREXC(c) (c) +#define trex_strlen strlen +#define trex_printf printf +#endif + +#ifndef TREX_API +#define TREX_API extern +#endif + +#define TRex_True 1 +#define TRex_False 0 + +#define TREX_ICASE ARG_REX_ICASE + +typedef unsigned int TRexBool; +typedef struct TRex TRex; + +typedef struct { + const TRexChar *begin; + int len; +} TRexMatch; + +TREX_API TRex *trex_compile(const TRexChar *pattern, const TRexChar **error, int flags); +TREX_API void trex_free(TRex *exp); +TREX_API TRexBool trex_match(TRex* exp, const TRexChar* text); +TREX_API TRexBool trex_search(TRex* exp, const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end); +TREX_API TRexBool trex_searchrange(TRex* exp, const TRexChar* text_begin, const TRexChar* text_end, const TRexChar** out_begin, const TRexChar** out_end); +TREX_API int trex_getsubexpcount(TRex* exp); +TREX_API TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch *subexp); + +#ifdef __cplusplus +} +#endif + +#endif + + + +struct privhdr +{ + const char *pattern; + int flags; +}; + + +static void arg_rex_resetfn(struct arg_rex *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + +static int arg_rex_scanfn(struct arg_rex *parent, const char *argval) +{ + int errorcode = 0; + const TRexChar *error = NULL; + TRex *rex = NULL; + TRexBool is_match = TRex_False; + + if (parent->count == parent->hdr.maxcount ) + { + /* maximum number of arguments exceeded */ + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* a valid argument with no argument value was given. */ + /* This happens when an optional argument value was invoked. */ + /* leave parent argument value unaltered but still count the argument. */ + parent->count++; + } + else + { + struct privhdr *priv = (struct privhdr *)parent->hdr.priv; + + /* test the current argument value for a match with the regular expression */ + /* if a match is detected, record the argument value in the arg_rex struct */ + + rex = trex_compile(priv->pattern, &error, priv->flags); + is_match = trex_match(rex, argval); + if (!is_match) + errorcode = EREGNOMATCH; + else + parent->sval[parent->count++] = argval; + + trex_free(rex); + } + + ARG_TRACE(("%s:scanfn(%p) returns %d\n",__FILE__,parent,errorcode)); + return errorcode; +} + +static int arg_rex_checkfn(struct arg_rex *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + //struct privhdr *priv = (struct privhdr*)parent->hdr.priv; + + /* free the regex "program" we constructed in resetfn */ + //regfree(&(priv->regex)); + + /*printf("%s:checkfn(%p) returns %d\n",__FILE__,parent,errorcode);*/ + return errorcode; +} + +static void arg_rex_errorfn(struct arg_rex *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + case EREGNOMATCH: + fputs("illegal value ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + + default: + { + //char errbuff[256]; + //regerror(errorcode, NULL, errbuff, sizeof(errbuff)); + //printf("%s\n", errbuff); + } + break; + } +} + + +struct arg_rex * arg_rex0(const char * shortopts, + const char * longopts, + const char * pattern, + const char *datatype, + int flags, + const char *glossary) +{ + return arg_rexn(shortopts, + longopts, + pattern, + datatype, + 0, + 1, + flags, + glossary); +} + +struct arg_rex * arg_rex1(const char * shortopts, + const char * longopts, + const char * pattern, + const char *datatype, + int flags, + const char *glossary) +{ + return arg_rexn(shortopts, + longopts, + pattern, + datatype, + 1, + 1, + flags, + glossary); +} + + +struct arg_rex * arg_rexn(const char * shortopts, + const char * longopts, + const char * pattern, + const char *datatype, + int mincount, + int maxcount, + int flags, + const char *glossary) +{ + size_t nbytes; + struct arg_rex *result; + struct privhdr *priv; + int i; + const TRexChar *error = NULL; + TRex *rex = NULL; + + if (!pattern) + { + printf( + "argtable: ERROR - illegal regular expression pattern \"(NULL)\"\n"); + printf("argtable: Bad argument table.\n"); + return NULL; + } + + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + nbytes = sizeof(struct arg_rex) /* storage for struct arg_rex */ + + sizeof(struct privhdr) /* storage for private arg_rex data */ + + maxcount * sizeof(char *); /* storage for sval[maxcount] array */ + + result = (struct arg_rex *)malloc(nbytes); + if (result == NULL) + return result; + + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = datatype ? datatype : pattern; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_rex_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_rex_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_rex_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_rex_errorfn; + + /* store the arg_rex_priv struct immediately after the arg_rex struct */ + result->hdr.priv = result + 1; + priv = (struct privhdr *)(result->hdr.priv); + priv->pattern = pattern; + priv->flags = flags; + + /* store the sval[maxcount] array immediately after the arg_rex_priv struct */ + result->sval = (const char * *)(priv + 1); + result->count = 0; + + /* foolproof the string pointers by initializing them to reference empty strings */ + for (i = 0; i < maxcount; i++) + result->sval[i] = ""; + + /* here we construct and destroy a regex representation of the regular + * expression for no other reason than to force any regex errors to be + * trapped now rather than later. If we don't, then errors may go undetected + * until an argument is actually parsed. + */ + + rex = trex_compile(priv->pattern, &error, priv->flags); + if (rex == NULL) + { + ARG_LOG(("argtable: %s \"%s\"\n", error ? error : _TREXC("undefined"), priv->pattern)); + ARG_LOG(("argtable: Bad argument table.\n")); + } + + trex_free(rex); + + ARG_TRACE(("arg_rexn() returns %p\n", result)); + return result; +} + + + +/* see copyright notice in trex.h */ +#include +#include +#include +#include + +#ifdef _UINCODE +#define scisprint iswprint +#define scstrlen wcslen +#define scprintf wprintf +#define _SC(x) L(x) +#else +#define scisprint isprint +#define scstrlen strlen +#define scprintf printf +#define _SC(x) (x) +#endif + +#ifdef _DEBUG +#include + +static const TRexChar *g_nnames[] = +{ + _SC("NONE"),_SC("OP_GREEDY"), _SC("OP_OR"), + _SC("OP_EXPR"),_SC("OP_NOCAPEXPR"),_SC("OP_DOT"), _SC("OP_CLASS"), + _SC("OP_CCLASS"),_SC("OP_NCLASS"),_SC("OP_RANGE"),_SC("OP_CHAR"), + _SC("OP_EOL"),_SC("OP_BOL"),_SC("OP_WB") +}; + +#endif +#define OP_GREEDY (MAX_CHAR+1) // * + ? {n} +#define OP_OR (MAX_CHAR+2) +#define OP_EXPR (MAX_CHAR+3) //parentesis () +#define OP_NOCAPEXPR (MAX_CHAR+4) //parentesis (?:) +#define OP_DOT (MAX_CHAR+5) +#define OP_CLASS (MAX_CHAR+6) +#define OP_CCLASS (MAX_CHAR+7) +#define OP_NCLASS (MAX_CHAR+8) //negates class the [^ +#define OP_RANGE (MAX_CHAR+9) +#define OP_CHAR (MAX_CHAR+10) +#define OP_EOL (MAX_CHAR+11) +#define OP_BOL (MAX_CHAR+12) +#define OP_WB (MAX_CHAR+13) + +#define TREX_SYMBOL_ANY_CHAR ('.') +#define TREX_SYMBOL_GREEDY_ONE_OR_MORE ('+') +#define TREX_SYMBOL_GREEDY_ZERO_OR_MORE ('*') +#define TREX_SYMBOL_GREEDY_ZERO_OR_ONE ('?') +#define TREX_SYMBOL_BRANCH ('|') +#define TREX_SYMBOL_END_OF_STRING ('$') +#define TREX_SYMBOL_BEGINNING_OF_STRING ('^') +#define TREX_SYMBOL_ESCAPE_CHAR ('\\') + + +typedef int TRexNodeType; + +typedef struct tagTRexNode{ + TRexNodeType type; + int left; + int right; + int next; +}TRexNode; + +struct TRex{ + const TRexChar *_eol; + const TRexChar *_bol; + const TRexChar *_p; + int _first; + int _op; + TRexNode *_nodes; + int _nallocated; + int _nsize; + int _nsubexpr; + TRexMatch *_matches; + int _currsubexp; + void *_jmpbuf; + const TRexChar **_error; + int _flags; +}; + +static int trex_list(TRex *exp); + +static int trex_newnode(TRex *exp, TRexNodeType type) +{ + TRexNode n; + int newid; + n.type = type; + n.next = n.right = n.left = -1; + if(type == OP_EXPR) + n.right = exp->_nsubexpr++; + if(exp->_nallocated < (exp->_nsize + 1)) { + exp->_nallocated *= 2; + exp->_nodes = (TRexNode *)realloc(exp->_nodes, exp->_nallocated * sizeof(TRexNode)); + } + exp->_nodes[exp->_nsize++] = n; + newid = exp->_nsize - 1; + return (int)newid; +} + +static void trex_error(TRex *exp,const TRexChar *error) +{ + if(exp->_error) *exp->_error = error; + longjmp(*((jmp_buf*)exp->_jmpbuf),-1); +} + +static void trex_expect(TRex *exp, int n){ + if((*exp->_p) != n) + trex_error(exp, _SC("expected paren")); + exp->_p++; +} + +static TRexChar trex_escapechar(TRex *exp) +{ + if(*exp->_p == TREX_SYMBOL_ESCAPE_CHAR){ + exp->_p++; + switch(*exp->_p) { + case 'v': exp->_p++; return '\v'; + case 'n': exp->_p++; return '\n'; + case 't': exp->_p++; return '\t'; + case 'r': exp->_p++; return '\r'; + case 'f': exp->_p++; return '\f'; + default: return (*exp->_p++); + } + } else if(!scisprint(*exp->_p)) trex_error(exp,_SC("letter expected")); + return (*exp->_p++); +} + +static int trex_charclass(TRex *exp,int classid) +{ + int n = trex_newnode(exp,OP_CCLASS); + exp->_nodes[n].left = classid; + return n; +} + +static int trex_charnode(TRex *exp,TRexBool isclass) +{ + TRexChar t; + if(*exp->_p == TREX_SYMBOL_ESCAPE_CHAR) { + exp->_p++; + switch(*exp->_p) { + case 'n': exp->_p++; return trex_newnode(exp,'\n'); + case 't': exp->_p++; return trex_newnode(exp,'\t'); + case 'r': exp->_p++; return trex_newnode(exp,'\r'); + case 'f': exp->_p++; return trex_newnode(exp,'\f'); + case 'v': exp->_p++; return trex_newnode(exp,'\v'); + case 'a': case 'A': case 'w': case 'W': case 's': case 'S': + case 'd': case 'D': case 'x': case 'X': case 'c': case 'C': + case 'p': case 'P': case 'l': case 'u': + { + t = *exp->_p; exp->_p++; + return trex_charclass(exp,t); + } + case 'b': + case 'B': + if(!isclass) { + int node = trex_newnode(exp,OP_WB); + exp->_nodes[node].left = *exp->_p; + exp->_p++; + return node; + } //else default + default: + t = *exp->_p; exp->_p++; + return trex_newnode(exp,t); + } + } + else if(!scisprint(*exp->_p)) { + + trex_error(exp,_SC("letter expected")); + } + t = *exp->_p; exp->_p++; + return trex_newnode(exp,t); +} +static int trex_class(TRex *exp) +{ + int ret = -1; + int first = -1,chain; + if(*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING){ + ret = trex_newnode(exp,OP_NCLASS); + exp->_p++; + }else ret = trex_newnode(exp,OP_CLASS); + + if(*exp->_p == ']') trex_error(exp,_SC("empty class")); + chain = ret; + while(*exp->_p != ']' && exp->_p != exp->_eol) { + if(*exp->_p == '-' && first != -1){ + int r,t; + if(*exp->_p++ == ']') trex_error(exp,_SC("unfinished range")); + r = trex_newnode(exp,OP_RANGE); + if(first>*exp->_p) trex_error(exp,_SC("invalid range")); + if(exp->_nodes[first].type == OP_CCLASS) trex_error(exp,_SC("cannot use character classes in ranges")); + exp->_nodes[r].left = exp->_nodes[first].type; + t = trex_escapechar(exp); + exp->_nodes[r].right = t; + exp->_nodes[chain].next = r; + chain = r; + first = -1; + } + else{ + if(first!=-1){ + int c = first; + exp->_nodes[chain].next = c; + chain = c; + first = trex_charnode(exp,TRex_True); + } + else{ + first = trex_charnode(exp,TRex_True); + } + } + } + if(first!=-1){ + int c = first; + exp->_nodes[chain].next = c; + chain = c; + first = -1; + } + /* hack? */ + exp->_nodes[ret].left = exp->_nodes[ret].next; + exp->_nodes[ret].next = -1; + return ret; +} + +static int trex_parsenumber(TRex *exp) +{ + int ret = *exp->_p-'0'; + int positions = 10; + exp->_p++; + while(isdigit(*exp->_p)) { + ret = ret*10+(*exp->_p++-'0'); + if(positions==1000000000) trex_error(exp,_SC("overflow in numeric constant")); + positions *= 10; + }; + return ret; +} + +static int trex_element(TRex *exp) +{ + int ret = -1; + switch(*exp->_p) + { + case '(': { + int expr,newn; + exp->_p++; + + + if(*exp->_p =='?') { + exp->_p++; + trex_expect(exp,':'); + expr = trex_newnode(exp,OP_NOCAPEXPR); + } + else + expr = trex_newnode(exp,OP_EXPR); + newn = trex_list(exp); + exp->_nodes[expr].left = newn; + ret = expr; + trex_expect(exp,')'); + } + break; + case '[': + exp->_p++; + ret = trex_class(exp); + trex_expect(exp,']'); + break; + case TREX_SYMBOL_END_OF_STRING: exp->_p++; ret = trex_newnode(exp,OP_EOL);break; + case TREX_SYMBOL_ANY_CHAR: exp->_p++; ret = trex_newnode(exp,OP_DOT);break; + default: + ret = trex_charnode(exp,TRex_False); + break; + } + + { + TRexBool isgreedy = TRex_False; + unsigned short p0 = 0, p1 = 0; + switch(*exp->_p){ + case TREX_SYMBOL_GREEDY_ZERO_OR_MORE: p0 = 0; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break; + case TREX_SYMBOL_GREEDY_ONE_OR_MORE: p0 = 1; p1 = 0xFFFF; exp->_p++; isgreedy = TRex_True; break; + case TREX_SYMBOL_GREEDY_ZERO_OR_ONE: p0 = 0; p1 = 1; exp->_p++; isgreedy = TRex_True; break; + case '{': + exp->_p++; + if(!isdigit(*exp->_p)) trex_error(exp,_SC("number expected")); + p0 = (unsigned short)trex_parsenumber(exp); + /*******************************/ + switch(*exp->_p) { + case '}': + p1 = p0; exp->_p++; + break; + case ',': + exp->_p++; + p1 = 0xFFFF; + if(isdigit(*exp->_p)){ + p1 = (unsigned short)trex_parsenumber(exp); + } + trex_expect(exp,'}'); + break; + default: + trex_error(exp,_SC(", or } expected")); + } + /*******************************/ + isgreedy = TRex_True; + break; + + } + if(isgreedy) { + int nnode = trex_newnode(exp,OP_GREEDY); + exp->_nodes[nnode].left = ret; + exp->_nodes[nnode].right = ((p0)<<16)|p1; + ret = nnode; + } + } + if((*exp->_p != TREX_SYMBOL_BRANCH) && (*exp->_p != ')') && (*exp->_p != TREX_SYMBOL_GREEDY_ZERO_OR_MORE) && (*exp->_p != TREX_SYMBOL_GREEDY_ONE_OR_MORE) && (*exp->_p != '\0')) { + int nnode = trex_element(exp); + exp->_nodes[ret].next = nnode; + } + + return ret; +} + +static int trex_list(TRex *exp) +{ + int ret=-1,e; + if(*exp->_p == TREX_SYMBOL_BEGINNING_OF_STRING) { + exp->_p++; + ret = trex_newnode(exp,OP_BOL); + } + e = trex_element(exp); + if(ret != -1) { + exp->_nodes[ret].next = e; + } + else ret = e; + + if(*exp->_p == TREX_SYMBOL_BRANCH) { + int temp,tright; + exp->_p++; + temp = trex_newnode(exp,OP_OR); + exp->_nodes[temp].left = ret; + tright = trex_list(exp); + exp->_nodes[temp].right = tright; + ret = temp; + } + return ret; +} + +static TRexBool trex_matchcclass(int cclass,TRexChar c) +{ + switch(cclass) { + case 'a': return isalpha(c)?TRex_True:TRex_False; + case 'A': return !isalpha(c)?TRex_True:TRex_False; + case 'w': return (isalnum(c) || c == '_')?TRex_True:TRex_False; + case 'W': return (!isalnum(c) && c != '_')?TRex_True:TRex_False; + case 's': return ISSPACE(c)?TRex_True:TRex_False; + case 'S': return !ISSPACE(c)?TRex_True:TRex_False; + case 'd': return isdigit(c)?TRex_True:TRex_False; + case 'D': return !isdigit(c)?TRex_True:TRex_False; + case 'x': return isxdigit(c)?TRex_True:TRex_False; + case 'X': return !isxdigit(c)?TRex_True:TRex_False; + case 'c': return iscntrl(c)?TRex_True:TRex_False; + case 'C': return !iscntrl(c)?TRex_True:TRex_False; + case 'p': return ispunct(c)?TRex_True:TRex_False; + case 'P': return !ispunct(c)?TRex_True:TRex_False; + case 'l': return islower(c)?TRex_True:TRex_False; + case 'u': return isupper(c)?TRex_True:TRex_False; + } + return TRex_False; /*cannot happen*/ +} + +static TRexBool trex_matchclass(TRex* exp,TRexNode *node,TRexChar c) +{ + do { + switch(node->type) { + case OP_RANGE: + if (exp->_flags & TREX_ICASE) + { + if(c >= toupper(node->left) && c <= toupper(node->right)) return TRex_True; + if(c >= tolower(node->left) && c <= tolower(node->right)) return TRex_True; + } + else + { + if(c >= node->left && c <= node->right) return TRex_True; + } + break; + case OP_CCLASS: + if(trex_matchcclass(node->left,c)) return TRex_True; + break; + default: + if (exp->_flags & TREX_ICASE) + { + if (c == tolower(node->type) || c == toupper(node->type)) return TRex_True; + } + else + { + if(c == node->type)return TRex_True; + } + + } + } while((node->next != -1) && (node = &exp->_nodes[node->next])); + return TRex_False; +} + +static const TRexChar *trex_matchnode(TRex* exp,TRexNode *node,const TRexChar *str,TRexNode *next) +{ + + TRexNodeType type = node->type; + switch(type) { + case OP_GREEDY: { + //TRexNode *greedystop = (node->next != -1) ? &exp->_nodes[node->next] : NULL; + TRexNode *greedystop = NULL; + int p0 = (node->right >> 16)&0x0000FFFF, p1 = node->right&0x0000FFFF, nmaches = 0; + const TRexChar *s=str, *good = str; + + if(node->next != -1) { + greedystop = &exp->_nodes[node->next]; + } + else { + greedystop = next; + } + + while((nmaches == 0xFFFF || nmaches < p1)) { + + const TRexChar *stop; + if(!(s = trex_matchnode(exp,&exp->_nodes[node->left],s,greedystop))) + break; + nmaches++; + good=s; + if(greedystop) { + //checks that 0 matches satisfy the expression(if so skips) + //if not would always stop(for instance if is a '?') + if(greedystop->type != OP_GREEDY || + (greedystop->type == OP_GREEDY && ((greedystop->right >> 16)&0x0000FFFF) != 0)) + { + TRexNode *gnext = NULL; + if(greedystop->next != -1) { + gnext = &exp->_nodes[greedystop->next]; + }else if(next && next->next != -1){ + gnext = &exp->_nodes[next->next]; + } + stop = trex_matchnode(exp,greedystop,s,gnext); + if(stop) { + //if satisfied stop it + if(p0 == p1 && p0 == nmaches) break; + else if(nmaches >= p0 && p1 == 0xFFFF) break; + else if(nmaches >= p0 && nmaches <= p1) break; + } + } + } + + if(s >= exp->_eol) + break; + } + if(p0 == p1 && p0 == nmaches) return good; + else if(nmaches >= p0 && p1 == 0xFFFF) return good; + else if(nmaches >= p0 && nmaches <= p1) return good; + return NULL; + } + case OP_OR: { + const TRexChar *asd = str; + TRexNode *temp=&exp->_nodes[node->left]; + while( (asd = trex_matchnode(exp,temp,asd,NULL)) ) { + if(temp->next != -1) + temp = &exp->_nodes[temp->next]; + else + return asd; + } + asd = str; + temp = &exp->_nodes[node->right]; + while( (asd = trex_matchnode(exp,temp,asd,NULL)) ) { + if(temp->next != -1) + temp = &exp->_nodes[temp->next]; + else + return asd; + } + return NULL; + break; + } + case OP_EXPR: + case OP_NOCAPEXPR:{ + TRexNode *n = &exp->_nodes[node->left]; + const TRexChar *cur = str; + int capture = -1; + if(node->type != OP_NOCAPEXPR && node->right == exp->_currsubexp) { + capture = exp->_currsubexp; + exp->_matches[capture].begin = cur; + exp->_currsubexp++; + } + + do { + TRexNode *subnext = NULL; + if(n->next != -1) { + subnext = &exp->_nodes[n->next]; + }else { + subnext = next; + } + if(!(cur = trex_matchnode(exp,n,cur,subnext))) { + if(capture != -1){ + exp->_matches[capture].begin = 0; + exp->_matches[capture].len = 0; + } + return NULL; + } + } while((n->next != -1) && (n = &exp->_nodes[n->next])); + + if(capture != -1) + exp->_matches[capture].len = (int)(cur - exp->_matches[capture].begin); + return cur; + } + case OP_WB: + if((str == exp->_bol && !ISSPACE(*str)) + || ((str == exp->_eol && !ISSPACE(*(str-1)))) + || ((!ISSPACE(*str) && ISSPACE(*(str+1)))) + || ((ISSPACE(*str) && !ISSPACE(*(str+1)))) ) { + return (node->left == 'b')?str:NULL; + } + return (node->left == 'b')?NULL:str; + case OP_BOL: + if(str == exp->_bol) return str; + return NULL; + case OP_EOL: + if(str == exp->_eol) return str; + return NULL; + case OP_DOT: + str++; + return str; + case OP_NCLASS: + case OP_CLASS: + if(trex_matchclass(exp,&exp->_nodes[node->left],*str)?(type == OP_CLASS?TRex_True:TRex_False):(type == OP_NCLASS?TRex_True:TRex_False)) { + str++; + return str; + } + return NULL; + case OP_CCLASS: + if(trex_matchcclass(node->left,*str)) { + str++; + return str; + } + return NULL; + default: /* char */ + if (exp->_flags & TREX_ICASE) + { + if(*str != tolower(node->type) && *str != toupper(node->type)) return NULL; + } + else + { + if (*str != node->type) return NULL; + } + str++; + return str; + } + return NULL; +} + +/* public api */ +TRex *trex_compile(const TRexChar *pattern,const TRexChar **error,int flags) +{ + TRex *exp = (TRex *)malloc(sizeof(TRex)); + exp->_eol = exp->_bol = NULL; + exp->_p = pattern; + exp->_nallocated = (int)scstrlen(pattern) * sizeof(TRexChar); + exp->_nodes = (TRexNode *)malloc(exp->_nallocated * sizeof(TRexNode)); + exp->_nsize = 0; + exp->_matches = 0; + exp->_nsubexpr = 0; + exp->_first = trex_newnode(exp,OP_EXPR); + exp->_error = error; + exp->_jmpbuf = malloc(sizeof(jmp_buf)); + exp->_flags = flags; + if(setjmp(*((jmp_buf*)exp->_jmpbuf)) == 0) { + int res = trex_list(exp); + exp->_nodes[exp->_first].left = res; + if(*exp->_p!='\0') + trex_error(exp,_SC("unexpected character")); +#ifdef _DEBUG + { + int nsize,i; + TRexNode *t; + nsize = exp->_nsize; + t = &exp->_nodes[0]; + scprintf(_SC("\n")); + for(i = 0;i < nsize; i++) { + if(exp->_nodes[i].type>MAX_CHAR) + scprintf(_SC("[%02d] %10s "),i,g_nnames[exp->_nodes[i].type-MAX_CHAR]); + else + scprintf(_SC("[%02d] %10c "),i,exp->_nodes[i].type); + scprintf(_SC("left %02d right %02d next %02d\n"),exp->_nodes[i].left,exp->_nodes[i].right,exp->_nodes[i].next); + } + scprintf(_SC("\n")); + } +#endif + exp->_matches = (TRexMatch *) malloc(exp->_nsubexpr * sizeof(TRexMatch)); + memset(exp->_matches,0,exp->_nsubexpr * sizeof(TRexMatch)); + } + else{ + trex_free(exp); + return NULL; + } + return exp; +} + +void trex_free(TRex *exp) +{ + if(exp) { + if(exp->_nodes) free(exp->_nodes); + if(exp->_jmpbuf) free(exp->_jmpbuf); + if(exp->_matches) free(exp->_matches); + free(exp); + } +} + +TRexBool trex_match(TRex* exp,const TRexChar* text) +{ + const TRexChar* res = NULL; + exp->_bol = text; + exp->_eol = text + scstrlen(text); + exp->_currsubexp = 0; + res = trex_matchnode(exp,exp->_nodes,text,NULL); + if(res == NULL || res != exp->_eol) + return TRex_False; + return TRex_True; +} + +TRexBool trex_searchrange(TRex* exp,const TRexChar* text_begin,const TRexChar* text_end,const TRexChar** out_begin, const TRexChar** out_end) +{ + const TRexChar *cur = NULL; + int node = exp->_first; + if(text_begin >= text_end) return TRex_False; + exp->_bol = text_begin; + exp->_eol = text_end; + do { + cur = text_begin; + while(node != -1) { + exp->_currsubexp = 0; + cur = trex_matchnode(exp,&exp->_nodes[node],cur,NULL); + if(!cur) + break; + node = exp->_nodes[node].next; + } + text_begin++; + } while(cur == NULL && text_begin != text_end); + + if(cur == NULL) + return TRex_False; + + --text_begin; + + if(out_begin) *out_begin = text_begin; + if(out_end) *out_end = cur; + return TRex_True; +} + +TRexBool trex_search(TRex* exp,const TRexChar* text, const TRexChar** out_begin, const TRexChar** out_end) +{ + return trex_searchrange(exp,text,text + scstrlen(text),out_begin,out_end); +} + +int trex_getsubexpcount(TRex* exp) +{ + return exp->_nsubexpr; +} + +TRexBool trex_getsubexp(TRex* exp, int n, TRexMatch *subexp) +{ + if( n<0 || n >= exp->_nsubexpr) return TRex_False; + *subexp = exp->_matches[n]; + return TRex_True; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include + +#include "argtable3.h" + + +static void arg_str_resetfn(struct arg_str *parent) +{ + ARG_TRACE(("%s:resetfn(%p)\n", __FILE__, parent)); + parent->count = 0; +} + + +static int arg_str_scanfn(struct arg_str *parent, const char *argval) +{ + int errorcode = 0; + + if (parent->count == parent->hdr.maxcount) + { + /* maximum number of arguments exceeded */ + errorcode = EMAXCOUNT; + } + else if (!argval) + { + /* a valid argument with no argument value was given. */ + /* This happens when an optional argument value was invoked. */ + /* leave parent arguiment value unaltered but still count the argument. */ + parent->count++; + } + else + { + parent->sval[parent->count++] = argval; + } + + ARG_TRACE(("%s:scanfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static int arg_str_checkfn(struct arg_str *parent) +{ + int errorcode = (parent->count < parent->hdr.mincount) ? EMINCOUNT : 0; + + ARG_TRACE(("%s:checkfn(%p) returns %d\n", __FILE__, parent, errorcode)); + return errorcode; +} + + +static void arg_str_errorfn( + struct arg_str *parent, + FILE *fp, + int errorcode, + const char *argval, + const char *progname) +{ + const char *shortopts = parent->hdr.shortopts; + const char *longopts = parent->hdr.longopts; + const char *datatype = parent->hdr.datatype; + + /* make argval NULL safe */ + argval = argval ? argval : ""; + + fprintf(fp, "%s: ", progname); + switch(errorcode) + { + case EMINCOUNT: + fputs("missing option ", fp); + arg_print_option(fp, shortopts, longopts, datatype, "\n"); + break; + + case EMAXCOUNT: + fputs("excess option ", fp); + arg_print_option(fp, shortopts, longopts, argval, "\n"); + break; + } +} + + +struct arg_str * arg_str0( + const char *shortopts, + const char *longopts, + const char *datatype, + const char *glossary) +{ + return arg_strn(shortopts, longopts, datatype, 0, 1, glossary); +} + + +struct arg_str * arg_str1( + const char *shortopts, + const char *longopts, + const char *datatype, + const char *glossary) +{ + return arg_strn(shortopts, longopts, datatype, 1, 1, glossary); +} + + +struct arg_str * arg_strn( + const char *shortopts, + const char *longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary) +{ + size_t nbytes; + struct arg_str *result; + + /* should not allow this stupid error */ + /* we should return an error code warning this logic error */ + /* foolproof things by ensuring maxcount is not less than mincount */ + maxcount = (maxcount < mincount) ? mincount : maxcount; + + nbytes = sizeof(struct arg_str) /* storage for struct arg_str */ + + maxcount * sizeof(char *); /* storage for sval[maxcount] array */ + + result = (struct arg_str *)malloc(nbytes); + if (result) + { + int i; + + /* init the arg_hdr struct */ + result->hdr.flag = ARG_HASVALUE; + result->hdr.shortopts = shortopts; + result->hdr.longopts = longopts; + result->hdr.datatype = datatype ? datatype : ""; + result->hdr.glossary = glossary; + result->hdr.mincount = mincount; + result->hdr.maxcount = maxcount; + result->hdr.parent = result; + result->hdr.resetfn = (arg_resetfn *)arg_str_resetfn; + result->hdr.scanfn = (arg_scanfn *)arg_str_scanfn; + result->hdr.checkfn = (arg_checkfn *)arg_str_checkfn; + result->hdr.errorfn = (arg_errorfn *)arg_str_errorfn; + + /* store the sval[maxcount] array immediately after the arg_str struct */ + result->sval = (const char * *)(result + 1); + result->count = 0; + + /* foolproof the string pointers by initialising them to reference empty strings */ + for (i = 0; i < maxcount; i++) + result->sval[i] = ""; + } + + ARG_TRACE(("arg_strn() returns %p\n", result)); + return result; +} +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#include +#include +#include +#include + +#include "argtable3.h" + +static +void arg_register_error(struct arg_end *end, + void *parent, + int error, + const char *argval) +{ + /* printf("arg_register_error(%p,%p,%d,%s)\n",end,parent,error,argval); */ + if (end->count < end->hdr.maxcount) + { + end->error[end->count] = error; + end->parent[end->count] = parent; + end->argval[end->count] = argval; + end->count++; + } + else + { + end->error[end->hdr.maxcount - 1] = ARG_ELIMIT; + end->parent[end->hdr.maxcount - 1] = end; + end->argval[end->hdr.maxcount - 1] = NULL; + } +} + + +/* + * Return index of first table entry with a matching short option + * or -1 if no match was found. + */ +static +int find_shortoption(struct arg_hdr * *table, char shortopt) +{ + int tabindex; + for(tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + if (table[tabindex]->shortopts && + strchr(table[tabindex]->shortopts, shortopt)) + return tabindex; + } + return -1; +} + + +struct longoptions +{ + int getoptval; + int noptions; + struct option *options; +}; + +#if 0 +static +void dump_longoptions(struct longoptions * longoptions) +{ + int i; + printf("getoptval = %d\n", longoptions->getoptval); + printf("noptions = %d\n", longoptions->noptions); + for (i = 0; i < longoptions->noptions; i++) + { + printf("options[%d].name = \"%s\"\n", + i, + longoptions->options[i].name); + printf("options[%d].has_arg = %d\n", i, longoptions->options[i].has_arg); + printf("options[%d].flag = %p\n", i, longoptions->options[i].flag); + printf("options[%d].val = %d\n", i, longoptions->options[i].val); + } +} +#endif + +static +struct longoptions * alloc_longoptions(struct arg_hdr * *table) +{ + struct longoptions *result; + size_t nbytes; + int noptions = 1; + size_t longoptlen = 0; + int tabindex; + + /* + * Determine the total number of option structs required + * by counting the number of comma separated long options + * in all table entries and return the count in noptions. + * note: noptions starts at 1 not 0 because we getoptlong + * requires a NULL option entry to terminate the option array. + * While we are at it, count the number of chars required + * to store private copies of all the longoption strings + * and return that count in logoptlen. + */ + tabindex = 0; + do + { + const char *longopts = table[tabindex]->longopts; + longoptlen += (longopts ? strlen(longopts) : 0) + 1; + while (longopts) + { + noptions++; + longopts = strchr(longopts + 1, ','); + } + } while(!(table[tabindex++]->flag & ARG_TERMINATOR)); + /*printf("%d long options consuming %d chars in total\n",noptions,longoptlen);*/ + + + /* allocate storage for return data structure as: */ + /* (struct longoptions) + (struct options)[noptions] + char[longoptlen] */ + nbytes = sizeof(struct longoptions) + + sizeof(struct option) * noptions + + longoptlen; + result = (struct longoptions *)malloc(nbytes); + if (result) + { + int option_index = 0; + char *store; + + result->getoptval = 0; + result->noptions = noptions; + result->options = (struct option *)(result + 1); + store = (char *)(result->options + noptions); + + for(tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + const char *longopts = table[tabindex]->longopts; + + while(longopts && *longopts) + { + char *storestart = store; + + /* copy progressive longopt strings into the store */ + while (*longopts != 0 && *longopts != ',') + *store++ = *longopts++; + *store++ = 0; + if (*longopts == ',') + longopts++; + /*fprintf(stderr,"storestart=\"%s\"\n",storestart);*/ + + result->options[option_index].name = storestart; + result->options[option_index].flag = &(result->getoptval); + result->options[option_index].val = tabindex; + if (table[tabindex]->flag & ARG_HASOPTVALUE) + result->options[option_index].has_arg = 2; + else if (table[tabindex]->flag & ARG_HASVALUE) + result->options[option_index].has_arg = 1; + else + result->options[option_index].has_arg = 0; + + option_index++; + } + } + /* terminate the options array with a zero-filled entry */ + result->options[option_index].name = 0; + result->options[option_index].has_arg = 0; + result->options[option_index].flag = 0; + result->options[option_index].val = 0; + } + + /*dump_longoptions(result);*/ + return result; +} + +static +char * alloc_shortoptions(struct arg_hdr * *table) +{ + char *result; + size_t len = 2; + int tabindex; + + /* determine the total number of option chars required */ + for(tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + struct arg_hdr *hdr = table[tabindex]; + len += 3 * (hdr->shortopts ? strlen(hdr->shortopts) : 0); + } + + result = malloc(len); + if (result) + { + char *res = result; + + /* add a leading ':' so getopt return codes distinguish */ + /* unrecognised option and options missing argument values */ + *res++ = ':'; + + for(tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + struct arg_hdr *hdr = table[tabindex]; + const char *shortopts = hdr->shortopts; + while(shortopts && *shortopts) + { + *res++ = *shortopts++; + if (hdr->flag & ARG_HASVALUE) + *res++ = ':'; + if (hdr->flag & ARG_HASOPTVALUE) + *res++ = ':'; + } + } + /* null terminate the string */ + *res = 0; + } + + /*printf("alloc_shortoptions() returns \"%s\"\n",(result?result:"NULL"));*/ + return result; +} + + +/* return index of the table terminator entry */ +static +int arg_endindex(struct arg_hdr * *table) +{ + int tabindex = 0; + while (!(table[tabindex]->flag & ARG_TERMINATOR)) + tabindex++; + return tabindex; +} + + +static +void arg_parse_tagged(int argc, + char * *argv, + struct arg_hdr * *table, + struct arg_end *endtable) +{ + struct longoptions *longoptions; + char *shortoptions; + int copt; + + /*printf("arg_parse_tagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ + + /* allocate short and long option arrays for the given opttable[]. */ + /* if the allocs fail then put an error msg in the last table entry. */ + longoptions = alloc_longoptions(table); + shortoptions = alloc_shortoptions(table); + if (!longoptions || !shortoptions) + { + /* one or both memory allocs failed */ + arg_register_error(endtable, endtable, ARG_EMALLOC, NULL); + /* free anything that was allocated (this is null safe) */ + free(shortoptions); + free(longoptions); + return; + } + + /*dump_longoptions(longoptions);*/ + + /* reset getopts internal option-index to zero, and disable error reporting */ + optind = 0; + opterr = 0; + + /* fetch and process args using getopt_long */ + while( (copt = + getopt_long(argc, argv, shortoptions, longoptions->options, + NULL)) != -1) + { + /* + printf("optarg='%s'\n",optarg); + printf("optind=%d\n",optind); + printf("copt=%c\n",(char)copt); + printf("optopt=%c (%d)\n",optopt, (int)(optopt)); + */ + switch(copt) + { + case 0: + { + int tabindex = longoptions->getoptval; + void *parent = table[tabindex]->parent; + /*printf("long option detected from argtable[%d]\n", tabindex);*/ + if (optarg && optarg[0] == 0 && + (table[tabindex]->flag & ARG_HASVALUE)) + { + /* printf(": long option %s requires an argument\n",argv[optind-1]); */ + arg_register_error(endtable, endtable, ARG_EMISSARG, + argv[optind - 1]); + /* continue to scan the (empty) argument value to enforce argument count checking */ + } + if (table[tabindex]->scanfn) + { + int errorcode = table[tabindex]->scanfn(parent, optarg); + if (errorcode != 0) + arg_register_error(endtable, parent, errorcode, optarg); + } + } + break; + + case '?': + /* + * getopt_long() found an unrecognised short option. + * if it was a short option its value is in optopt + * if it was a long option then optopt=0 + */ + switch (optopt) + { + case 0: + /*printf("?0 unrecognised long option %s\n",argv[optind-1]);*/ + arg_register_error(endtable, endtable, ARG_ELONGOPT, + argv[optind - 1]); + break; + default: + /*printf("?* unrecognised short option '%c'\n",optopt);*/ + arg_register_error(endtable, endtable, optopt, NULL); + break; + } + break; + + case ':': + /* + * getopt_long() found an option with its argument missing. + */ + /*printf(": option %s requires an argument\n",argv[optind-1]); */ + arg_register_error(endtable, endtable, ARG_EMISSARG, + argv[optind - 1]); + break; + + default: + { + /* getopt_long() found a valid short option */ + int tabindex = find_shortoption(table, (char)copt); + /*printf("short option detected from argtable[%d]\n", tabindex);*/ + if (tabindex == -1) + { + /* should never get here - but handle it just in case */ + /*printf("unrecognised short option %d\n",copt);*/ + arg_register_error(endtable, endtable, copt, NULL); + } + else + { + if (table[tabindex]->scanfn) + { + void *parent = table[tabindex]->parent; + int errorcode = table[tabindex]->scanfn(parent, optarg); + if (errorcode != 0) + arg_register_error(endtable, parent, errorcode, optarg); + } + } + break; + } + } + } + + free(shortoptions); + free(longoptions); +} + + +static +void arg_parse_untagged(int argc, + char * *argv, + struct arg_hdr * *table, + struct arg_end *endtable) +{ + int tabindex = 0; + int errorlast = 0; + const char *optarglast = NULL; + void *parentlast = NULL; + + /*printf("arg_parse_untagged(%d,%p,%p,%p)\n",argc,argv,table,endtable);*/ + while (!(table[tabindex]->flag & ARG_TERMINATOR)) + { + void *parent; + int errorcode; + + /* if we have exhausted our argv[optind] entries then we have finished */ + if (optind >= argc) + { + /*printf("arg_parse_untagged(): argv[] exhausted\n");*/ + return; + } + + /* skip table entries with non-null long or short options (they are not untagged entries) */ + if (table[tabindex]->longopts || table[tabindex]->shortopts) + { + /*printf("arg_parse_untagged(): skipping argtable[%d] (tagged argument)\n",tabindex);*/ + tabindex++; + continue; + } + + /* skip table entries with NULL scanfn */ + if (!(table[tabindex]->scanfn)) + { + /*printf("arg_parse_untagged(): skipping argtable[%d] (NULL scanfn)\n",tabindex);*/ + tabindex++; + continue; + } + + /* attempt to scan the current argv[optind] with the current */ + /* table[tabindex] entry. If it succeeds then keep it, otherwise */ + /* try again with the next table[] entry. */ + parent = table[tabindex]->parent; + errorcode = table[tabindex]->scanfn(parent, argv[optind]); + if (errorcode == 0) + { + /* success, move onto next argv[optind] but stay with same table[tabindex] */ + /*printf("arg_parse_untagged(): argtable[%d] successfully matched\n",tabindex);*/ + optind++; + + /* clear the last tentative error */ + errorlast = 0; + } + else + { + /* failure, try same argv[optind] with next table[tabindex] entry */ + /*printf("arg_parse_untagged(): argtable[%d] failed match\n",tabindex);*/ + tabindex++; + + /* remember this as a tentative error we may wish to reinstate later */ + errorlast = errorcode; + optarglast = argv[optind]; + parentlast = parent; + } + + } + + /* if a tenative error still remains at this point then register it as a proper error */ + if (errorlast) + { + arg_register_error(endtable, parentlast, errorlast, optarglast); + optind++; + } + + /* only get here when not all argv[] entries were consumed */ + /* register an error for each unused argv[] entry */ + while (optind < argc) + { + /*printf("arg_parse_untagged(): argv[%d]=\"%s\" not consumed\n",optind,argv[optind]);*/ + arg_register_error(endtable, endtable, ARG_ENOMATCH, argv[optind++]); + } + + return; +} + + +static +void arg_parse_check(struct arg_hdr * *table, struct arg_end *endtable) +{ + int tabindex = 0; + /* printf("arg_parse_check()\n"); */ + do + { + if (table[tabindex]->checkfn) + { + void *parent = table[tabindex]->parent; + int errorcode = table[tabindex]->checkfn(parent); + if (errorcode != 0) + arg_register_error(endtable, parent, errorcode, NULL); + } + } while(!(table[tabindex++]->flag & ARG_TERMINATOR)); +} + + +static +void arg_reset(void * *argtable) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int tabindex = 0; + /*printf("arg_reset(%p)\n",argtable);*/ + do + { + if (table[tabindex]->resetfn) + table[tabindex]->resetfn(table[tabindex]->parent); + } while(!(table[tabindex++]->flag & ARG_TERMINATOR)); +} + + +int arg_parse(int argc, char * *argv, void * *argtable) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + struct arg_end *endtable; + int endindex; + char * *argvcopy = NULL; + + /*printf("arg_parse(%d,%p,%p)\n",argc,argv,argtable);*/ + + /* reset any argtable data from previous invocations */ + arg_reset(argtable); + + /* locate the first end-of-table marker within the array */ + endindex = arg_endindex(table); + endtable = (struct arg_end *)table[endindex]; + + /* Special case of argc==0. This can occur on Texas Instruments DSP. */ + /* Failure to trap this case results in an unwanted NULL result from */ + /* the malloc for argvcopy (next code block). */ + if (argc == 0) + { + /* We must still perform post-parse checks despite the absence of command line arguments */ + arg_parse_check(table, endtable); + + /* Now we are finished */ + return endtable->count; + } + + argvcopy = (char **)malloc(sizeof(char *) * (argc + 1)); + if (argvcopy) + { + int i; + + /* + Fill in the local copy of argv[]. We need a local copy + because getopt rearranges argv[] which adversely affects + susbsequent parsing attempts. + */ + for (i = 0; i < argc; i++) + argvcopy[i] = argv[i]; + + argvcopy[argc] = NULL; + + /* parse the command line (local copy) for tagged options */ + arg_parse_tagged(argc, argvcopy, table, endtable); + + /* parse the command line (local copy) for untagged options */ + arg_parse_untagged(argc, argvcopy, table, endtable); + + /* if no errors so far then perform post-parse checks otherwise dont bother */ + if (endtable->count == 0) + arg_parse_check(table, endtable); + + /* release the local copt of argv[] */ + free(argvcopy); + } + else + { + /* memory alloc failed */ + arg_register_error(endtable, endtable, ARG_EMALLOC, NULL); + } + + return endtable->count; +} + + +/* + * Concatenate contents of src[] string onto *pdest[] string. + * The *pdest pointer is altered to point to the end of the + * target string and *pndest is decremented by the same number + * of chars. + * Does not append more than *pndest chars into *pdest[] + * so as to prevent buffer overruns. + * Its something like strncat() but more efficient for repeated + * calls on the same destination string. + * Example of use: + * char dest[30] = "good" + * size_t ndest = sizeof(dest); + * char *pdest = dest; + * arg_char(&pdest,"bye ",&ndest); + * arg_char(&pdest,"cruel ",&ndest); + * arg_char(&pdest,"world!",&ndest); + * Results in: + * dest[] == "goodbye cruel world!" + * ndest == 10 + */ +static +void arg_cat(char * *pdest, const char *src, size_t *pndest) +{ + char *dest = *pdest; + char *end = dest + *pndest; + + /*locate null terminator of dest string */ + while(dest < end && *dest != 0) + dest++; + + /* concat src string to dest string */ + while(dest < end && *src != 0) + *dest++ = *src++; + + /* null terminate dest string */ + *dest = 0; + + /* update *pdest and *pndest */ + *pndest = end - dest; + *pdest = dest; +} + + +static +void arg_cat_option(char *dest, + size_t ndest, + const char *shortopts, + const char *longopts, + const char *datatype, + int optvalue) +{ + if (shortopts) + { + char option[3]; + + /* note: option array[] is initialiazed dynamically here to satisfy */ + /* a deficiency in the watcom compiler wrt static array initializers. */ + option[0] = '-'; + option[1] = shortopts[0]; + option[2] = 0; + + arg_cat(&dest, option, &ndest); + if (datatype) + { + arg_cat(&dest, " ", &ndest); + if (optvalue) + { + arg_cat(&dest, "[", &ndest); + arg_cat(&dest, datatype, &ndest); + arg_cat(&dest, "]", &ndest); + } + else + arg_cat(&dest, datatype, &ndest); + } + } + else if (longopts) + { + size_t ncspn; + + /* add "--" tag prefix */ + arg_cat(&dest, "--", &ndest); + + /* add comma separated option tag */ + ncspn = strcspn(longopts, ","); +#ifdef __STDC_WANT_SECURE_LIB__ + strncat_s(dest, ndest, longopts, (ncspn < ndest) ? ncspn : ndest); +#else + strncat(dest, longopts, (ncspn < ndest) ? ncspn : ndest); +#endif + + if (datatype) + { + arg_cat(&dest, "=", &ndest); + if (optvalue) + { + arg_cat(&dest, "[", &ndest); + arg_cat(&dest, datatype, &ndest); + arg_cat(&dest, "]", &ndest); + } + else + arg_cat(&dest, datatype, &ndest); + } + } + else if (datatype) + { + if (optvalue) + { + arg_cat(&dest, "[", &ndest); + arg_cat(&dest, datatype, &ndest); + arg_cat(&dest, "]", &ndest); + } + else + arg_cat(&dest, datatype, &ndest); + } +} + +static +void arg_cat_optionv(char *dest, + size_t ndest, + const char *shortopts, + const char *longopts, + const char *datatype, + int optvalue, + const char *separator) +{ + separator = separator ? separator : ""; + + if (shortopts) + { + const char *c = shortopts; + while(*c) + { + /* "-a|-b|-c" */ + char shortopt[3]; + + /* note: shortopt array[] is initialiazed dynamically here to satisfy */ + /* a deficiency in the watcom compiler wrt static array initializers. */ + shortopt[0] = '-'; + shortopt[1] = *c; + shortopt[2] = 0; + + arg_cat(&dest, shortopt, &ndest); + if (*++c) + arg_cat(&dest, separator, &ndest); + } + } + + /* put separator between long opts and short opts */ + if (shortopts && longopts) + arg_cat(&dest, separator, &ndest); + + if (longopts) + { + const char *c = longopts; + while(*c) + { + size_t ncspn; + + /* add "--" tag prefix */ + arg_cat(&dest, "--", &ndest); + + /* add comma separated option tag */ + ncspn = strcspn(c, ","); +#ifdef __STDC_WANT_SECURE_LIB__ + strncat_s(dest, ndest, c, (ncspn < ndest) ? ncspn : ndest); +#else + strncat(dest, c, (ncspn < ndest) ? ncspn : ndest); +#endif + c += ncspn; + + /* add given separator in place of comma */ + if (*c == ',') + { + arg_cat(&dest, separator, &ndest); + c++; + } + } + } + + if (datatype) + { + if (longopts) + arg_cat(&dest, "=", &ndest); + else if (shortopts) + arg_cat(&dest, " ", &ndest); + + if (optvalue) + { + arg_cat(&dest, "[", &ndest); + arg_cat(&dest, datatype, &ndest); + arg_cat(&dest, "]", &ndest); + } + else + arg_cat(&dest, datatype, &ndest); + } +} + + +/* this function should be deprecated because it doesnt consider optional argument values (ARG_HASOPTVALUE) */ +void arg_print_option(FILE *fp, + const char *shortopts, + const char *longopts, + const char *datatype, + const char *suffix) +{ + char syntax[200] = ""; + suffix = suffix ? suffix : ""; + + /* there is no way of passing the proper optvalue for optional argument values here, so we must ignore it */ + arg_cat_optionv(syntax, + sizeof(syntax), + shortopts, + longopts, + datatype, + 0, + "|"); + + fputs(syntax, fp); + fputs(suffix, fp); +} + + +/* + * Print a GNU style [OPTION] string in which all short options that + * do not take argument values are presented in abbreviated form, as + * in: -xvfsd, or -xvf[sd], or [-xvsfd] + */ +static +void arg_print_gnuswitch(FILE *fp, struct arg_hdr * *table) +{ + int tabindex; + char *format1 = " -%c"; + char *format2 = " [-%c"; + char *suffix = ""; + + /* print all mandatory switches that are without argument values */ + for(tabindex = 0; + table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); + tabindex++) + { + /* skip optional options */ + if (table[tabindex]->mincount < 1) + continue; + + /* skip non-short options */ + if (table[tabindex]->shortopts == NULL) + continue; + + /* skip options that take argument values */ + if (table[tabindex]->flag & ARG_HASVALUE) + continue; + + /* print the short option (only the first short option char, ignore multiple choices)*/ + fprintf(fp, format1, table[tabindex]->shortopts[0]); + format1 = "%c"; + format2 = "[%c"; + } + + /* print all optional switches that are without argument values */ + for(tabindex = 0; + table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); + tabindex++) + { + /* skip mandatory args */ + if (table[tabindex]->mincount > 0) + continue; + + /* skip args without short options */ + if (table[tabindex]->shortopts == NULL) + continue; + + /* skip args with values */ + if (table[tabindex]->flag & ARG_HASVALUE) + continue; + + /* print first short option */ + fprintf(fp, format2, table[tabindex]->shortopts[0]); + format2 = "%c"; + suffix = "]"; + } + + fprintf(fp, "%s", suffix); +} + + +void arg_print_syntax(FILE *fp, void * *argtable, const char *suffix) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int i, tabindex; + + /* print GNU style [OPTION] string */ + arg_print_gnuswitch(fp, table); + + /* print remaining options in abbreviated style */ + for(tabindex = 0; + table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); + tabindex++) + { + char syntax[200] = ""; + const char *shortopts, *longopts, *datatype; + + /* skip short options without arg values (they were printed by arg_print_gnu_switch) */ + if (table[tabindex]->shortopts && + !(table[tabindex]->flag & ARG_HASVALUE)) + continue; + + shortopts = table[tabindex]->shortopts; + longopts = table[tabindex]->longopts; + datatype = table[tabindex]->datatype; + arg_cat_option(syntax, + sizeof(syntax), + shortopts, + longopts, + datatype, + table[tabindex]->flag & ARG_HASOPTVALUE); + + if (strlen(syntax) > 0) + { + /* print mandatory instances of this option */ + for (i = 0; i < table[tabindex]->mincount; i++) + fprintf(fp, " %s", syntax); + + /* print optional instances enclosed in "[..]" */ + switch ( table[tabindex]->maxcount - table[tabindex]->mincount ) + { + case 0: + break; + case 1: + fprintf(fp, " [%s]", syntax); + break; + case 2: + fprintf(fp, " [%s] [%s]", syntax, syntax); + break; + default: + fprintf(fp, " [%s]...", syntax); + break; + } + } + } + + if (suffix) + fprintf(fp, "%s", suffix); +} + + +void arg_print_syntaxv(FILE *fp, void * *argtable, const char *suffix) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int i, tabindex; + + /* print remaining options in abbreviated style */ + for(tabindex = 0; + table[tabindex] && !(table[tabindex]->flag & ARG_TERMINATOR); + tabindex++) + { + char syntax[200] = ""; + const char *shortopts, *longopts, *datatype; + + shortopts = table[tabindex]->shortopts; + longopts = table[tabindex]->longopts; + datatype = table[tabindex]->datatype; + arg_cat_optionv(syntax, + sizeof(syntax), + shortopts, + longopts, + datatype, + table[tabindex]->flag & ARG_HASOPTVALUE, + "|"); + + /* print mandatory options */ + for (i = 0; i < table[tabindex]->mincount; i++) + fprintf(fp, " %s", syntax); + + /* print optional args enclosed in "[..]" */ + switch ( table[tabindex]->maxcount - table[tabindex]->mincount ) + { + case 0: + break; + case 1: + fprintf(fp, " [%s]", syntax); + break; + case 2: + fprintf(fp, " [%s] [%s]", syntax, syntax); + break; + default: + fprintf(fp, " [%s]...", syntax); + break; + } + } + + if (suffix) + fprintf(fp, "%s", suffix); +} + + +void arg_print_glossary(FILE *fp, void * *argtable, const char *format) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int tabindex; + + format = format ? format : " %-20s %s\n"; + for (tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + if (table[tabindex]->glossary) + { + char syntax[200] = ""; + const char *shortopts = table[tabindex]->shortopts; + const char *longopts = table[tabindex]->longopts; + const char *datatype = table[tabindex]->datatype; + const char *glossary = table[tabindex]->glossary; + arg_cat_optionv(syntax, + sizeof(syntax), + shortopts, + longopts, + datatype, + table[tabindex]->flag & ARG_HASOPTVALUE, + ", "); + fprintf(fp, format, syntax, glossary); + } + } +} + + +/** + * Print a piece of text formatted, which means in a column with a + * left and a right margin. The lines are wrapped at whitspaces next + * to right margin. The function does not indent the first line, but + * only the following ones. + * + * Example: + * arg_print_formatted( fp, 0, 5, "Some text that doesn't fit." ) + * will result in the following output: + * + * Some + * text + * that + * doesn' + * t fit. + * + * Too long lines will be wrapped in the middle of a word. + * + * arg_print_formatted( fp, 2, 7, "Some text that doesn't fit." ) + * will result in the following output: + * + * Some + * text + * that + * doesn' + * t fit. + * + * As you see, the first line is not indented. This enables output of + * lines, which start in a line where output already happened. + * + * Author: Uli Fouquet + */ +static +void arg_print_formatted( FILE *fp, + const unsigned lmargin, + const unsigned rmargin, + const char *text ) +{ + const unsigned textlen = (unsigned)strlen( text ); + unsigned line_start = 0; + unsigned line_end = textlen + 1; + const unsigned colwidth = (rmargin - lmargin) + 1; + + /* Someone doesn't like us... */ + if ( line_end < line_start ) + { fprintf( fp, "%s\n", text ); } + + while (line_end - 1 > line_start ) + { + /* Eat leading whitespaces. This is essential because while + wrapping lines, there will often be a whitespace at beginning + of line */ + while ( ISSPACE(*(text + line_start)) ) + { line_start++; } + + if ((line_end - line_start) > colwidth ) + { line_end = line_start + colwidth; } + + /* Find last whitespace, that fits into line */ + while ( ( line_end > line_start ) + && ( line_end - line_start > colwidth ) + && !ISSPACE(*(text + line_end))) + { line_end--; } + + /* Do not print trailing whitespace. If this text + has got only one line, line_end now points to the + last char due to initialization. */ + line_end--; + + /* Output line of text */ + while ( line_start < line_end ) + { + fputc(*(text + line_start), fp ); + line_start++; + } + fputc( '\n', fp ); + + /* Initialize another line */ + if ( line_end + 1 < textlen ) + { + unsigned i; + + for (i = 0; i < lmargin; i++ ) + { fputc( ' ', fp ); } + + line_end = textlen; + } + + /* If we have to print another line, get also the last char. */ + line_end++; + + } /* lines of text */ +} + +/** + * Prints the glossary in strict GNU format. + * Differences to arg_print_glossary() are: + * - wraps lines after 80 chars + * - indents lines without shortops + * - does not accept formatstrings + * + * Contributed by Uli Fouquet + */ +void arg_print_glossary_gnu(FILE *fp, void * *argtable ) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int tabindex; + + for(tabindex = 0; !(table[tabindex]->flag & ARG_TERMINATOR); tabindex++) + { + if (table[tabindex]->glossary) + { + char syntax[200] = ""; + const char *shortopts = table[tabindex]->shortopts; + const char *longopts = table[tabindex]->longopts; + const char *datatype = table[tabindex]->datatype; + const char *glossary = table[tabindex]->glossary; + + if ( !shortopts && longopts ) + { + /* Indent trailing line by 4 spaces... */ + memset( syntax, ' ', 4 ); + *(syntax + 4) = '\0'; + } + + arg_cat_optionv(syntax, + sizeof(syntax), + shortopts, + longopts, + datatype, + table[tabindex]->flag & ARG_HASOPTVALUE, + ", "); + + /* If syntax fits not into column, print glossary in new line... */ + if ( strlen(syntax) > 25 ) + { + fprintf( fp, " %-25s %s\n", syntax, "" ); + *syntax = '\0'; + } + + fprintf( fp, " %-25s ", syntax ); + arg_print_formatted( fp, 28, 79, glossary ); + } + } /* for each table entry */ + + fputc( '\n', fp ); +} + + +/** + * Checks the argtable[] array for NULL entries and returns 1 + * if any are found, zero otherwise. + */ +int arg_nullcheck(void * *argtable) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int tabindex; + /*printf("arg_nullcheck(%p)\n",argtable);*/ + + if (!table) + return 1; + + tabindex = 0; + do + { + /*printf("argtable[%d]=%p\n",tabindex,argtable[tabindex]);*/ + if (!table[tabindex]) + return 1; + } while(!(table[tabindex++]->flag & ARG_TERMINATOR)); + + return 0; +} + + +/* + * arg_free() is deprecated in favour of arg_freetable() due to a flaw in its design. + * The flaw results in memory leak in the (very rare) case that an intermediate + * entry in the argtable array failed its memory allocation while others following + * that entry were still allocated ok. Those subsequent allocations will not be + * deallocated by arg_free(). + * Despite the unlikeliness of the problem occurring, and the even unlikelier event + * that it has any deliterious effect, it is fixed regardless by replacing arg_free() + * with the newer arg_freetable() function. + * We still keep arg_free() for backwards compatibility. + */ +void arg_free(void * *argtable) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + int tabindex = 0; + int flag; + /*printf("arg_free(%p)\n",argtable);*/ + do + { + /* + if we encounter a NULL entry then somewhat incorrectly we presume + we have come to the end of the array. It isnt strictly true because + an intermediate entry could be NULL with other non-NULL entries to follow. + The subsequent argtable entries would then not be freed as they should. + */ + if (table[tabindex] == NULL) + break; + + flag = table[tabindex]->flag; + free(table[tabindex]); + table[tabindex++] = NULL; + + } while(!(flag & ARG_TERMINATOR)); +} + +/* frees each non-NULL element of argtable[], where n is the size of the number of entries in the array */ +void arg_freetable(void * *argtable, size_t n) +{ + struct arg_hdr * *table = (struct arg_hdr * *)argtable; + size_t tabindex = 0; + /*printf("arg_freetable(%p)\n",argtable);*/ + for (tabindex = 0; tabindex < n; tabindex++) + { + if (table[tabindex] == NULL) + continue; + + free(table[tabindex]); + table[tabindex] = NULL; + }; +} + diff --git a/client/cliparser/argtable3.h b/client/cliparser/argtable3.h new file mode 100644 index 00000000..1107de25 --- /dev/null +++ b/client/cliparser/argtable3.h @@ -0,0 +1,305 @@ +/******************************************************************************* + * This file is part of the argtable3 library. + * + * Copyright (C) 1998-2001,2003-2011,2013 Stewart Heitmann + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of STEWART HEITMANN nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL STEWART HEITMANN BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + ******************************************************************************/ + +#ifndef ARGTABLE3 +#define ARGTABLE3 + +#include /* FILE */ +#include /* struct tm */ + +#ifdef __cplusplus +extern "C" { +#endif + +#define ARG_REX_ICASE 1 + +/* bit masks for arg_hdr.flag */ +enum +{ + ARG_TERMINATOR=0x1, + ARG_HASVALUE=0x2, + ARG_HASOPTVALUE=0x4 +}; + +typedef void (arg_resetfn)(void *parent); +typedef int (arg_scanfn)(void *parent, const char *argval); +typedef int (arg_checkfn)(void *parent); +typedef void (arg_errorfn)(void *parent, FILE *fp, int error, const char *argval, const char *progname); + + +/* +* The arg_hdr struct defines properties that are common to all arg_xxx structs. +* The argtable library requires each arg_xxx struct to have an arg_hdr +* struct as its first data member. +* The argtable library functions then use this data to identify the +* properties of the command line option, such as its option tags, +* datatype string, and glossary strings, and so on. +* Moreover, the arg_hdr struct contains pointers to custom functions that +* are provided by each arg_xxx struct which perform the tasks of parsing +* that particular arg_xxx arguments, performing post-parse checks, and +* reporting errors. +* These functions are private to the individual arg_xxx source code +* and are the pointer to them are initiliased by that arg_xxx struct's +* constructor function. The user could alter them after construction +* if desired, but the original intention is for them to be set by the +* constructor and left unaltered. +*/ +struct arg_hdr +{ + char flag; /* Modifier flags: ARG_TERMINATOR, ARG_HASVALUE. */ + const char *shortopts; /* String defining the short options */ + const char *longopts; /* String defiing the long options */ + const char *datatype; /* Description of the argument data type */ + const char *glossary; /* Description of the option as shown by arg_print_glossary function */ + int mincount; /* Minimum number of occurences of this option accepted */ + int maxcount; /* Maximum number of occurences if this option accepted */ + void *parent; /* Pointer to parent arg_xxx struct */ + arg_resetfn *resetfn; /* Pointer to parent arg_xxx reset function */ + arg_scanfn *scanfn; /* Pointer to parent arg_xxx scan function */ + arg_checkfn *checkfn; /* Pointer to parent arg_xxx check function */ + arg_errorfn *errorfn; /* Pointer to parent arg_xxx error function */ + void *priv; /* Pointer to private header data for use by arg_xxx functions */ +}; + +struct arg_rem +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ +}; + +struct arg_lit +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args */ +}; + +struct arg_int +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args */ + int *ival; /* Array of parsed argument values */ +}; + +struct arg_dbl +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args */ + double *dval; /* Array of parsed argument values */ +}; + +struct arg_str +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args */ + const char **sval; /* Array of parsed argument values */ +}; + +struct arg_rex +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args */ + const char **sval; /* Array of parsed argument values */ +}; + +struct arg_file +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of matching command line args*/ + const char **filename; /* Array of parsed filenames (eg: /home/foo.bar) */ + const char **basename; /* Array of parsed basenames (eg: foo.bar) */ + const char **extension; /* Array of parsed extensions (eg: .bar) */ +}; + +struct arg_date +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + const char *format; /* strptime format string used to parse the date */ + int count; /* Number of matching command line args */ + struct tm *tmval; /* Array of parsed time values */ +}; + +enum {ARG_ELIMIT=1, ARG_EMALLOC, ARG_ENOMATCH, ARG_ELONGOPT, ARG_EMISSARG}; +struct arg_end +{ + struct arg_hdr hdr; /* The mandatory argtable header struct */ + int count; /* Number of errors encountered */ + int *error; /* Array of error codes */ + void **parent; /* Array of pointers to offending arg_xxx struct */ + const char **argval; /* Array of pointers to offending argv[] string */ +}; + + +/**** arg_xxx constructor functions *********************************/ + +struct arg_rem* arg_rem(const char* datatype, const char* glossary); + +struct arg_lit* arg_lit0(const char* shortopts, + const char* longopts, + const char* glossary); +struct arg_lit* arg_lit1(const char* shortopts, + const char* longopts, + const char *glossary); +struct arg_lit* arg_litn(const char* shortopts, + const char* longopts, + int mincount, + int maxcount, + const char *glossary); + +struct arg_key* arg_key0(const char* keyword, + int flags, + const char* glossary); +struct arg_key* arg_key1(const char* keyword, + int flags, + const char* glossary); +struct arg_key* arg_keyn(const char* keyword, + int flags, + int mincount, + int maxcount, + const char* glossary); + +struct arg_int* arg_int0(const char* shortopts, + const char* longopts, + const char* datatype, + const char* glossary); +struct arg_int* arg_int1(const char* shortopts, + const char* longopts, + const char* datatype, + const char *glossary); +struct arg_int* arg_intn(const char* shortopts, + const char* longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary); + +struct arg_dbl* arg_dbl0(const char* shortopts, + const char* longopts, + const char* datatype, + const char* glossary); +struct arg_dbl* arg_dbl1(const char* shortopts, + const char* longopts, + const char* datatype, + const char *glossary); +struct arg_dbl* arg_dbln(const char* shortopts, + const char* longopts, + const char *datatype, + int mincount, + int maxcount, + const char *glossary); + +struct arg_str* arg_str0(const char* shortopts, + const char* longopts, + const char* datatype, + const char* glossary); +struct arg_str* arg_str1(const char* shortopts, + const char* longopts, + const char* datatype, + const char *glossary); +struct arg_str* arg_strn(const char* shortopts, + const char* longopts, + const char* datatype, + int mincount, + int maxcount, + const char *glossary); + +struct arg_rex* arg_rex0(const char* shortopts, + const char* longopts, + const char* pattern, + const char* datatype, + int flags, + const char* glossary); +struct arg_rex* arg_rex1(const char* shortopts, + const char* longopts, + const char* pattern, + const char* datatype, + int flags, + const char *glossary); +struct arg_rex* arg_rexn(const char* shortopts, + const char* longopts, + const char* pattern, + const char* datatype, + int mincount, + int maxcount, + int flags, + const char *glossary); + +struct arg_file* arg_file0(const char* shortopts, + const char* longopts, + const char* datatype, + const char* glossary); +struct arg_file* arg_file1(const char* shortopts, + const char* longopts, + const char* datatype, + const char *glossary); +struct arg_file* arg_filen(const char* shortopts, + const char* longopts, + const char* datatype, + int mincount, + int maxcount, + const char *glossary); + +struct arg_date* arg_date0(const char* shortopts, + const char* longopts, + const char* format, + const char* datatype, + const char* glossary); +struct arg_date* arg_date1(const char* shortopts, + const char* longopts, + const char* format, + const char* datatype, + const char *glossary); +struct arg_date* arg_daten(const char* shortopts, + const char* longopts, + const char* format, + const char* datatype, + int mincount, + int maxcount, + const char *glossary); + +struct arg_end* arg_end(int maxerrors); + + +/**** other functions *******************************************/ +int arg_nullcheck(void **argtable); +int arg_parse(int argc, char **argv, void **argtable); +void arg_print_option(FILE *fp, const char *shortopts, const char *longopts, const char *datatype, const char *suffix); +void arg_print_syntax(FILE *fp, void **argtable, const char *suffix); +void arg_print_syntaxv(FILE *fp, void **argtable, const char *suffix); +void arg_print_glossary(FILE *fp, void **argtable, const char *format); +void arg_print_glossary_gnu(FILE *fp, void **argtable); +void arg_print_errors(FILE* fp, struct arg_end* end, const char* progname); +void arg_freetable(void **argtable, size_t n); + +/**** deprecated functions, for back-compatibility only ********/ +void arg_free(void **argtable); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/client/cliparser/cliparser.c b/client/cliparser/cliparser.c new file mode 100644 index 00000000..0e70a630 --- /dev/null +++ b/client/cliparser/cliparser.c @@ -0,0 +1,167 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Command line parser core commands +//----------------------------------------------------------------------------- + +#include "cliparser.h" +#include +#include + +void **argtable = NULL; +size_t argtableLen = 0; +char *programName = NULL; +char *programHint = NULL; +char *programHelp = NULL; +char buf[500] = {0}; + +int CLIParserInit(char *vprogramName, char *vprogramHint, char *vprogramHelp) { + argtable = NULL; + argtableLen = 0; + programName = vprogramName; + programHint = vprogramHint; + programHelp = vprogramHelp; + memset(buf, 0x00, 500); + + return 0; +} + +int CLIParserParseArg(int argc, char **argv, void* vargtable[], size_t vargtableLen, bool allowEmptyExec) { + int nerrors; + + argtable = vargtable; + argtableLen = vargtableLen; + + /* verify the argtable[] entries were allocated sucessfully */ + if (arg_nullcheck(argtable) != 0) { + /* NULL entries were detected, some allocations must have failed */ + printf("ERROR: Insufficient memory\n"); + return 2; + } + /* Parse the command line as defined by argtable[] */ + nerrors = arg_parse(argc, argv, argtable); + + /* special case: '--help' takes precedence over error reporting */ + if ((argc < 2 && !allowEmptyExec) ||((struct arg_lit *)argtable[0])->count > 0) { // help must be the first record + printf("Usage: %s", programName); + arg_print_syntaxv(stdout, argtable, "\n"); + if (programHint) + printf("%s\n\n", programHint); + arg_print_glossary(stdout, argtable, " %-20s %s\n"); + printf("\n"); + if (programHelp) + printf("%s \n", programHelp); + + return 1; + } + + /* If the parser returned any errors then display them and exit */ + if (nerrors > 0) { + /* Display the error details contained in the arg_end struct.*/ + arg_print_errors(stdout, ((struct arg_end *)argtable[vargtableLen - 1]), programName); + printf("Try '%s --help' for more information.\n", programName); + + return 3; + } + + return 0; +} + +enum ParserState { + PS_FIRST, + PS_ARGUMENT, + PS_OPTION, +}; + +#define isSpace(c)(c == ' ' || c == '\t') + +int CLIParserParseString(const char* str, void* vargtable[], size_t vargtableLen, bool allowEmptyExec) { + int argc = 0; + char *argv[200] = {NULL}; + + int len = strlen(str); + char *bufptr = buf; + char *spaceptr = NULL; + enum ParserState state = PS_FIRST; + + argv[argc++] = bufptr; + // param0 = program name + memcpy(buf, programName, strlen(programName) + 1); // with 0x00 + bufptr += strlen(programName) + 1; + if (len) + argv[argc++] = bufptr; + + // parse params + for (int i = 0; i < len; i++) { + switch(state){ + case PS_FIRST: // first char + if (str[i] == '-'){ // first char before space is '-' - next element - option + state = PS_OPTION; + + if (spaceptr) { + bufptr = spaceptr; + *bufptr = 0x00; + bufptr++; + argv[argc++] = bufptr; + } + } + spaceptr = NULL; + case PS_ARGUMENT: + if (state == PS_FIRST) + state = PS_ARGUMENT; + if (isSpace(str[i])) { + spaceptr = bufptr; + state = PS_FIRST; + } + *bufptr = str[i]; + bufptr++; + break; + case PS_OPTION: + if (isSpace(str[i])){ + state = PS_FIRST; + + *bufptr = 0x00; + bufptr++; + argv[argc++] = bufptr; + break; + } + + *bufptr = str[i]; + bufptr++; + break; + } + } + + return CLIParserParseArg(argc, argv, vargtable, vargtableLen, allowEmptyExec); +} + +void CLIParserFree() { + arg_freetable(argtable, argtableLen); + argtable = NULL; + + return; +} + +// convertors +int CLIParamHexToBuf(struct arg_str *argstr, uint8_t *data, int maxdatalen, int *datalen) { + switch(param_gethex_to_eol(argstr->sval[0], 0, data, maxdatalen, datalen)) { + case 1: + printf("Parameter error: Invalid HEX value.\n"); + return 1; + case 2: + printf("Parameter error: parameter too large.\n"); + return 2; + case 3: + printf("Parameter error: Hex string must have even number of digits.\n"); + return 3; + } + + return 0; +} + + + diff --git a/client/cliparser/cliparser.h b/client/cliparser/cliparser.h new file mode 100644 index 00000000..3f4fa4cc --- /dev/null +++ b/client/cliparser/cliparser.h @@ -0,0 +1,32 @@ +//----------------------------------------------------------------------------- +// Copyright (C) 2017 Merlok +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// Command line parser core commands +//----------------------------------------------------------------------------- + +#include "argtable3.h" +#include "util.h" +#include + +#define arg_param_begin arg_lit0("hH", "help", "print this help and exit") +#define arg_param_end arg_end(20) + +#define arg_getsize(a) (sizeof(a) / sizeof(a[0])) +#define arg_get_lit(n)(((struct arg_lit*)argtable[n])->count) +#define arg_get_int(n)(((struct arg_int*)argtable[n])->ival[0]) +#define arg_get_str(n)((struct arg_str*)argtable[n]) + +#define CLIExecWithReturn(cmd, atbl, ifempty) if (CLIParserParseString(cmd, atbl, arg_getsize(atbl), ifempty)){CLIParserFree();return 0;} +#define CLIGetStrBLessWithReturn(paramnum, data, datalen, delta) if (CLIParamHexToBuf(arg_get_str(paramnum), data, sizeof(data) - (delta), datalen)) {CLIParserFree();return 1;} +#define CLIGetStrWithReturn(paramnum, data, datalen) if (CLIParamHexToBuf(arg_get_str(paramnum), data, sizeof(data), datalen)) {CLIParserFree();return 1;} + +extern int CLIParserInit(char *vprogramName, char *vprogramHint, char *vprogramHelp); +extern int CLIParserParseString(const char* str, void* argtable[], size_t vargtableLen, bool allowEmptyExec); +extern int CLIParserParseArg(int argc, char **argv, void* argtable[], size_t vargtableLen, bool allowEmptyExec); +extern void CLIParserFree(); + +extern int CLIParamHexToBuf(struct arg_str *argstr, uint8_t *data, int maxdatalen, int *datalen); diff --git a/client/cmdhf14a.c b/client/cmdhf14a.c index 03ab0b5a..2d76f109 100644 --- a/client/cmdhf14a.c +++ b/client/cmdhf14a.c @@ -28,6 +28,7 @@ #include "mifare.h" #include "cmdhfmfu.h" #include "mifarehost.h" +#include "cliparser/cliparser.h" #include "emv/apduinfo.h" #include "emv/emvcore.h" @@ -137,38 +138,32 @@ int CmdHF14AList(const char *Cmd) int CmdHF14AReader(const char *Cmd) { uint32_t cm = ISO14A_CONNECT; - bool disconnectAfter = true; + bool leaveSignalON = false; - int cmdp = 0; - while(param_getchar(Cmd, cmdp) != 0x00) { - switch(param_getchar(Cmd, cmdp)) { - case 'h': - case 'H': - PrintAndLog("Usage: hf 14a reader [k|x] [3]"); - PrintAndLog(" k keep the field active after command executed"); - PrintAndLog(" x just drop the signal field"); - PrintAndLog(" 3 ISO14443-3 select only (skip RATS)"); - return 0; - case '3': - cm |= ISO14A_NO_RATS; - break; - case 'k': - case 'K': - disconnectAfter = false; - break; - case 'x': - case 'X': - cm &= ~ISO14A_CONNECT; - break; - default: - PrintAndLog("Unknown command."); - return 1; - } - - cmdp++; + CLIParserInit("hf 14a reader", "Executes ISO1443A anticollision-select group of commands.", NULL); + void* argtable[] = { + arg_param_begin, + arg_lit0("kK", "keep", "keep the field active after command executed"), + arg_lit0("xX", "drop", "just drop the signal field"), + arg_lit0("3", NULL, "ISO14443-3 select only (skip RATS)"), + arg_param_end + }; + if (CLIParserParseString(Cmd, argtable, arg_getsize(argtable), true)){ + CLIParserFree(); + return 0; } - - if (!disconnectAfter) + + leaveSignalON = arg_get_lit(1); + if (arg_get_lit(2)) { + cm = cm - ISO14A_CONNECT; + } + if (arg_get_lit(3)) { + cm |= ISO14A_NO_RATS; + } + + CLIParserFree(); + + if (leaveSignalON) cm |= ISO14A_NO_DISCONNECT; UsbCommand c = {CMD_READER_ISO_14443a, {cm, 0, 0}}; @@ -200,12 +195,12 @@ int CmdHF14AReader(const char *Cmd) { if(card.ats_len >= 3) { // a valid ATS consists of at least the length byte (TL) and 2 CRC bytes PrintAndLog(" ATS : %s", sprint_hex(card.ats, card.ats_len)); } - if (!disconnectAfter) { + if (leaveSignalON) { PrintAndLog("Card is selected. You can now start sending commands"); } } - if (disconnectAfter) { + if (!leaveSignalON) { PrintAndLog("Field dropped."); } @@ -737,59 +732,30 @@ int CmdHF14AAPDU(const char *cmd) { bool activateField = false; bool leaveSignalON = false; bool decodeTLV = false; + + CLIParserInit("hf 14a apdu", + "Sends an ISO 7816-4 APDU via ISO 14443-4 block transmission protocol (T=CL)", + "Sample:\n\thf 14a apdu -st 00A404000E325041592E5359532E444446303100\n"); + + void* argtable[] = { + arg_param_begin, + arg_lit0("sS", "select", "activate field and select card"), + arg_lit0("kK", "keep", "leave the signal field ON after receive response"), + arg_lit0("tT", "tlv", "executes TLV decoder if it possible"), + arg_str1(NULL, NULL, "", NULL), + arg_param_end + }; + CLIExecWithReturn(cmd, argtable, false); - if (strlen(cmd) < 2) { - PrintAndLog("Usage: hf 14a apdu [-s] [-k] [-t] "); - PrintAndLog("Command sends an ISO 7816-4 APDU via ISO 14443-4 block transmission protocol (T=CL)"); - PrintAndLog(" -s activate field and select card"); - PrintAndLog(" -k leave the signal field ON after receive response"); - PrintAndLog(" -t executes TLV decoder if it possible. TODO!!!!"); - return 0; - } + activateField = arg_get_lit(1); + leaveSignalON = arg_get_lit(2); + decodeTLV = arg_get_lit(3); + // len = data + PCB(1b) + CRC(2b) + CLIGetStrBLessWithReturn(4, data, &datalen, 1 + 2); - int cmdp = 0; - while(param_getchar(cmd, cmdp) != 0x00) { - char c = param_getchar(cmd, cmdp); - if ((c == '-') && (param_getlength(cmd, cmdp) == 2)) - switch (param_getchar_indx(cmd, 1, cmdp)) { - case 's': - case 'S': - activateField = true; - break; - case 'k': - case 'K': - leaveSignalON = true; - break; - case 't': - case 'T': - decodeTLV = true; - break; - default: - PrintAndLog("Unknown parameter '%c'", param_getchar_indx(cmd, 1, cmdp)); - return 1; - } - - if (isxdigit((unsigned char)c)) { - // len = data + PCB(1b) + CRC(2b) - switch(param_gethex_to_eol(cmd, cmdp, data, sizeof(data) - 1 - 2, &datalen)) { - case 1: - PrintAndLog("Invalid HEX value."); - return 1; - case 2: - PrintAndLog("APDU too large."); - return 1; - case 3: - PrintAndLog("Hex must have even number of digits."); - return 1; - } - - // we get all the hex to end of line with spaces - break; - } - - cmdp++; - } + CLIParserFree(); +// PrintAndLog("---str [%d] %s", arg_get_str(4)->count, arg_get_str(4)->sval[0]); PrintAndLog(">>>>[%s%s%s] %s", activateField ? "sel ": "", leaveSignalON ? "keep ": "", decodeTLV ? "TLV": "", sprint_hex(data, datalen)); int res = ExchangeAPDU14a(data, datalen, activateField, leaveSignalON, data, USB_CMD_DATA_SIZE, &datalen); @@ -821,102 +787,57 @@ int CmdHF14ACmdRaw(const char *cmd) { bool bTimeout = false; uint32_t timeout = 0; bool topazmode = false; - char buf[5]=""; - int i = 0; uint8_t data[USB_CMD_DATA_SIZE]; - uint16_t datalen = 0; - uint32_t temp; + int datalen = 0; - if (strlen(cmd)<2) { - PrintAndLog("Usage: hf 14a raw [-r] [-c] [-p] [-f] [-b] [-t] <0A 0B 0C ... hex>"); - PrintAndLog(" -r do not read response"); - PrintAndLog(" -c calculate and append CRC"); - PrintAndLog(" -p leave the signal field ON after receive"); - PrintAndLog(" -a active signal field ON without select"); - PrintAndLog(" -s active signal field ON with select"); - PrintAndLog(" -b number of bits to send. Useful for send partial byte"); - PrintAndLog(" -t timeout in ms"); - PrintAndLog(" -T use Topaz protocol to send command"); - PrintAndLog(" -3 ISO14443-3 select only (skip RATS)"); + // extract parameters + CLIParserInit("hf 14a raw", "Send raw hex data to tag", + "Sample:\n"\ + "\thf 14a raw -pa -b7 -t1000 52 -- execute WUPA\n"\ + "\thf 14a raw -p 9320 -- anticollision\n"\ + "\thf 14a raw -psc 60 00 -- select and mifare AUTH\n"); + void* argtable[] = { + arg_param_begin, + arg_lit0("rR", "nreply", "do not read response"), + arg_lit0("cC", "crc", "calculate and append CRC"), + arg_lit0("pP", "power", "leave the signal field ON after receive"), + arg_lit0("aA", "active", "active signal field ON without select"), + arg_lit0("sS", "actives", "active signal field ON with select"), + arg_int0("bB", "bits", NULL, "number of bits to send. Useful for send partial byte"), + arg_int0("t", "timeout", NULL, "timeout in ms"), + arg_lit0("T", "topaz", "use Topaz protocol to send command"), + arg_lit0("3", NULL, "ISO14443-3 select only (skip RATS)"), + arg_str1(NULL, NULL, "", NULL), + arg_param_end + }; + // defaults + arg_get_int(6) = 0; + arg_get_int(7) = 0; + + if (CLIParserParseString(cmd, argtable, arg_getsize(argtable), false)){ + CLIParserFree(); return 0; } - - - // strip - while (*cmd==' ' || *cmd=='\t') cmd++; - - while (cmd[i]!='\0') { - if (cmd[i]==' ' || cmd[i]=='\t') { i++; continue; } - if (cmd[i]=='-') { - switch (cmd[i+1]) { - case 'r': - reply = false; - break; - case 'c': - crc = true; - break; - case 'p': - power = true; - break; - case 'a': - active = true; - break; - case 's': - active_select = true; - break; - case 'b': - sscanf(cmd+i+2,"%d",&temp); - numbits = temp & 0xFFFF; - i+=3; - while(cmd[i]!=' ' && cmd[i]!='\0') { i++; } - i-=2; - break; - case 't': - bTimeout = true; - sscanf(cmd+i+2,"%d",&temp); - timeout = temp; - i+=3; - while(cmd[i]!=' ' && cmd[i]!='\0') { i++; } - i-=2; - break; - case 'T': - topazmode = true; - break; - case '3': - no_rats = true; - break; - default: - PrintAndLog("Invalid option"); - return 0; - } - i+=2; - continue; - } - if ((cmd[i]>='0' && cmd[i]<='9') || - (cmd[i]>='a' && cmd[i]<='f') || - (cmd[i]>='A' && cmd[i]<='F') ) { - buf[strlen(buf)+1]=0; - buf[strlen(buf)]=cmd[i]; - i++; - - if (strlen(buf)>=2) { - sscanf(buf,"%x",&temp); - data[datalen]=(uint8_t)(temp & 0xff); - *buf=0; - if (datalen > sizeof(data)-1) { - if (crc) - PrintAndLog("Buffer is full, we can't add CRC to your data"); - break; - } else { - datalen++; - } - } - continue; - } - PrintAndLog("Invalid char on input"); - return 0; + + reply = !arg_get_lit(1); + crc = arg_get_lit(2); + power = arg_get_lit(3); + active = arg_get_lit(4); + active_select = arg_get_lit(5); + numbits = arg_get_int(6) & 0xFFFF; + timeout = arg_get_int(7); + bTimeout = (timeout > 0); + topazmode = arg_get_lit(8); + no_rats = arg_get_lit(9); + // len = data + CRC(2b) + if (CLIParamHexToBuf(arg_get_str(10), data, sizeof(data) -2, &datalen)) { + CLIParserFree(); + return 1; } - + + CLIParserFree(); + + // logic if(crc && datalen>0 && datalen Date: Sun, 9 Sep 2018 16:40:20 +0200 Subject: [PATCH 3/4] Legic Tag Simulator (#666) * FPGA Hi-Simulate: Formatted code * FPGA Hi-Simulate: Fixed documantation * FPGA Hi-Simulate: Freed up 4 LUTs * FPGA Hi-Simulate: Added 212kHz SSP-Clock option * Legic: Moved card simulator into separate file & cleaned interface. Reader and card simulation have almost no common code. Moreover the sim uses an SSP Clock at 212kHz for all timings to prevent any drifting from the PRNG. This clock speed is not available in reader simulation mode (SSP runs at up to 3.4MHz, and changes speed between TX and RX). For these reasons having the code in separate files makes it significantly cleaner. * Legic: Implemented RX and TX for card simulation * Legic: Implemented setup phase for card simulation * Legic: Implemented read command for card simulation * Legic: Implemented write command for card simulation --- armsrc/Makefile | 3 +- armsrc/appmain.c | 3 +- armsrc/legicrf.c | 427 +++------------------------------------- armsrc/legicrf.h | 2 +- armsrc/legicrfsim.c | 468 ++++++++++++++++++++++++++++++++++++++++++++ armsrc/legicrfsim.h | 19 ++ client/cmdhflegic.c | 8 +- fpga/fpga_hf.bit | Bin 42175 -> 42175 bytes fpga/hi_simulate.v | 44 ++--- 9 files changed, 539 insertions(+), 435 deletions(-) create mode 100644 armsrc/legicrfsim.c create mode 100644 armsrc/legicrfsim.h diff --git a/armsrc/Makefile b/armsrc/Makefile index d4b13c6b..046ad1bc 100644 --- a/armsrc/Makefile +++ b/armsrc/Makefile @@ -59,11 +59,12 @@ THUMBSRC = start.c \ # These are to be compiled in ARM mode ARMSRC = fpgaloader.c \ legicrf.c \ + legicrfsim.c \ + legic_prng.c \ $(SRC_ISO14443a) \ $(SRC_ISO14443b) \ $(SRC_CRAPTO1) \ $(SRC_CRC) \ - legic_prng.c \ iclass.c \ BigBuf.c \ optimized_cipher.c \ diff --git a/armsrc/appmain.c b/armsrc/appmain.c index 4034788a..f7bcd620 100644 --- a/armsrc/appmain.c +++ b/armsrc/appmain.c @@ -21,6 +21,7 @@ #include "printf.h" #include "string.h" #include "legicrf.h" +#include "legicrfsim.h" #include "hitag2.h" #include "hitagS.h" #include "lfsampling.h" @@ -1090,7 +1091,7 @@ void UsbPacketReceived(uint8_t *packet, int len) #ifdef WITH_LEGICRF case CMD_SIMULATE_TAG_LEGIC_RF: - LegicRfSimulate(c->arg[0], c->arg[1], c->arg[2]); + LegicRfSimulate(c->arg[0]); break; case CMD_WRITER_LEGIC_RF: diff --git a/armsrc/legicrf.c b/armsrc/legicrf.c index 2a236b6f..c8a4829f 100644 --- a/armsrc/legicrf.c +++ b/armsrc/legicrf.c @@ -1,7 +1,7 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Plötz // 2016 Iceman -// 2018 AntiCat (rwd rewritten) +// 2018 AntiCat // // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of @@ -20,30 +20,8 @@ #include "legic.h" #include "crc.h" -static struct legic_frame { - int bits; - uint32_t data; -} current_frame; - -static enum { - STATE_DISCON, - STATE_IV, - STATE_CON, -} legic_state; - -static crc_t legic_crc; -static int legic_read_count; -static uint32_t legic_prng_bc; -static uint32_t legic_prng_iv; - -static int legic_phase_drift; -static int legic_frame_drift; -static int legic_reqresp_drift; - -AT91PS_TC timer; -AT91PS_TC prng_timer; - static legic_card_select_t card;/* metadata of currently selected card */ +static crc_t legic_crc; //----------------------------------------------------------------------------- // Frame timing and pseudorandom number generator @@ -71,11 +49,6 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define TAG_BIT_PERIOD 150 /* 100us */ #define TAG_WRITE_TIMEOUT 60 /* 40 * 100us (write should take at most 3.6ms) */ -#define SIM_DIVISOR 586 /* prng_time/DIV count prng needs to be forwared */ -#define SIM_SHIFT 900 /* prng_time+SHIFT shift of delayed start */ -#define RWD_TIME_FUZZ 20 /* rather generous 13us, since the peak detector - /+ hysteresis fuzz quite a bit */ - #define LEGIC_READ 0x01 /* Read Command */ #define LEGIC_WRITE 0x00 /* Write Command */ @@ -86,8 +59,6 @@ static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ #define INPUT_THRESHOLD 8 /* heuristically determined, lower values */ /* lead to detecting false ack during write */ -#define FUZZ_EQUAL(value, target, fuzz) ((value) > ((target)-(fuzz)) && (value) < ((target)+(fuzz))) - //----------------------------------------------------------------------------- // I/O interface abstraction (FPGA -> ARM) //----------------------------------------------------------------------------- @@ -125,8 +96,8 @@ static inline uint8_t rx_byte_from_fpga() { // also. // // The demedulated should be alligned to the bit periode by the caller. This is -// done in rx_bit_as_reader and rx_ack_as_reader. -static inline bool rx_bit_as_reader() { +// done in rx_bit and rx_ack. +static inline bool rx_bit() { int32_t cq = 0; int32_t ci = 0; @@ -158,7 +129,7 @@ static inline bool rx_bit_as_reader() { // be circumvented, but the adventage over bitbang would be little. //----------------------------------------------------------------------------- -static inline void tx_bit_as_reader(bool bit) { +static inline void tx_bit(bool bit) { // insert pause LOW(GPIO_SSC_DOUT); last_frame_end += RWD_TIME_PAUSE; @@ -180,7 +151,7 @@ static inline void tx_bit_as_reader(bool bit) { // present. //----------------------------------------------------------------------------- -static void tx_frame_as_reader(uint32_t frame, uint8_t len) { +static void tx_frame(uint32_t frame, uint8_t len) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_TX); // wait for next tx timeslot @@ -190,7 +161,7 @@ static void tx_frame_as_reader(uint32_t frame, uint8_t len) { // transmit frame, MSB first for(uint8_t i = 0; i < len; ++i) { bool bit = (frame >> i) & 0x01; - tx_bit_as_reader(bit ^ legic_prng_get_bit()); + tx_bit(bit ^ legic_prng_get_bit()); legic_prng_forward(1); }; @@ -201,7 +172,7 @@ static void tx_frame_as_reader(uint32_t frame, uint8_t len) { HIGH(GPIO_SSC_DOUT); } -static uint32_t rx_frame_as_reader(uint8_t len) { +static uint32_t rx_frame(uint8_t len) { FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ | FPGA_HF_READER_RX_XCORR_QUARTER_FREQ); @@ -211,11 +182,11 @@ static uint32_t rx_frame_as_reader(uint8_t len) { while(GET_TICKS < last_frame_end) { }; uint32_t frame = 0; - for(uint8_t i = 0; i < len; i++) { - frame |= (rx_bit_as_reader() ^ legic_prng_get_bit()) << i; + for(uint8_t i = 0; i < len; ++i) { + frame |= (rx_bit() ^ legic_prng_get_bit()) << i; legic_prng_forward(1); - // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD last_frame_end += TAG_BIT_PERIOD; while(GET_TICKS < last_frame_end) { }; } @@ -223,7 +194,7 @@ static uint32_t rx_frame_as_reader(uint8_t len) { return frame; } -static bool rx_ack_as_reader() { +static bool rx_ack() { // change fpga into rx mode FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_READER_RX_XCORR | FPGA_HF_READER_RX_XCORR_848_KHZ @@ -236,10 +207,10 @@ static bool rx_ack_as_reader() { uint32_t ack = 0; for(uint8_t i = 0; i < TAG_WRITE_TIMEOUT; ++i) { // sample bit - ack = rx_bit_as_reader(); + ack = rx_bit(); legic_prng_forward(1); - // rx_bit_as_reader runs only 95us, resync to TAG_BIT_PERIOD + // rx_bit runs only 95us, resync to TAG_BIT_PERIOD last_frame_end += TAG_BIT_PERIOD; while(GET_TICKS < last_frame_end) { }; @@ -256,7 +227,7 @@ static bool rx_ack_as_reader() { // Legic Reader //----------------------------------------------------------------------------- -int init_card(uint8_t cardtype, legic_card_select_t *p_card) { +static int init_card(uint8_t cardtype, legic_card_select_t *p_card) { p_card->tagtype = cardtype; switch(p_card->tagtype) { @@ -313,8 +284,8 @@ static void init_reader(bool clear_mem) { // The setup consists of a three way handshake: // - Transmit initialisation vector 7 bits // - Receive card type 6 bits -// - Acknowledge frame 6 bits -static uint32_t setup_phase_reader(uint8_t iv) { +// - Transmit Acknowledge 6 bits +static uint32_t setup_phase(uint8_t iv) { // init coordination timestamp last_frame_end = GET_TICKS; @@ -323,24 +294,24 @@ static uint32_t setup_phase_reader(uint8_t iv) { while(GET_TICKS < last_frame_end) { }; legic_prng_init(0); - tx_frame_as_reader(iv, 7); + tx_frame(iv, 7); - // configure iv + // configure prng legic_prng_init(iv); legic_prng_forward(2); // receive card type - int32_t card_type = rx_frame_as_reader(6); + int32_t card_type = rx_frame(6); legic_prng_forward(3); // send obsfuscated acknowledgment frame switch (card_type) { case 0x0D: - tx_frame_as_reader(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 + tx_frame(0x19, 6); // MIM22 | READCMD = 0x18 | 0x01 break; case 0x1D: case 0x3D: - tx_frame_as_reader(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 + tx_frame(0x39, 6); // MIM256 | READCMD = 0x38 | 0x01 break; } @@ -359,9 +330,9 @@ static int16_t read_byte(uint16_t index, uint8_t cmd_sz) { // read one byte LED_B_ON(); legic_prng_forward(2); - tx_frame_as_reader(cmd, cmd_sz); + tx_frame(cmd, cmd_sz); legic_prng_forward(2); - uint32_t frame = rx_frame_as_reader(12); + uint32_t frame = rx_frame(12); LED_B_OFF(); // split frame into data and crc @@ -391,12 +362,12 @@ bool write_byte(uint16_t index, uint8_t byte, uint8_t addr_sz) { // send write command LED_C_ON(); legic_prng_forward(2); - tx_frame_as_reader(cmd, addr_sz + 1 + 8 + 4); // sz = addr_sz + cmd + data + crc + tx_frame(cmd, addr_sz + 1 + 8 + 4); // sz = addr_sz + cmd + data + crc legic_prng_forward(3); LED_C_OFF(); // wait for ack - return rx_ack_as_reader(); + return rx_ack(); } //----------------------------------------------------------------------------- @@ -413,7 +384,7 @@ void LegicRfReader(int offset, int bytes) { // establish shared secret and detect card type DbpString("Reading card ..."); - uint8_t card_type = setup_phase_reader(SESSION_IV); + uint8_t card_type = setup_phase(SESSION_IV); if(init_card(card_type, &card) != 0) { Dbprintf("No or unknown card found, aborting"); goto OUT; @@ -463,7 +434,7 @@ void LegicRfWriter(int bytes, int offset) { // establish shared secret and detect card type Dbprintf("Writing 0x%02.2x - 0x%02.2x ...", offset, offset+bytes); - uint8_t card_type = setup_phase_reader(SESSION_IV); + uint8_t card_type = setup_phase(SESSION_IV); if(init_card(card_type, &card) != 0) { Dbprintf("No or unknown card found, aborting"); goto OUT; @@ -492,345 +463,3 @@ OUT: LED_D_OFF(); StopTicks(); } - -//----------------------------------------------------------------------------- -// Legic Simulator -//----------------------------------------------------------------------------- - -static void setup_timer(void) -{ - /* Set up Timer 1 to use for measuring time between pulses. Since we're bit-banging - * this it won't be terribly accurate but should be good enough. - */ - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC1); - timer = AT91C_BASE_TC1; - timer->TC_CCR = AT91C_TC_CLKDIS; - timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV3_CLOCK; - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - - /* - * Set up Timer 2 to use for measuring time between frames in - * tag simulation mode. Runs 4x faster as Timer 1 - */ - AT91C_BASE_PMC->PMC_PCER = (1 << AT91C_ID_TC2); - prng_timer = AT91C_BASE_TC2; - prng_timer->TC_CCR = AT91C_TC_CLKDIS; - prng_timer->TC_CMR = AT91C_TC_CLKS_TIMER_DIV2_CLOCK; - prng_timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; -} - -/* Generate Keystream */ -static uint32_t get_key_stream(int skip, int count) -{ - uint32_t key=0; int i; - - /* Use int to enlarge timer tc to 32bit */ - legic_prng_bc += prng_timer->TC_CV; - prng_timer->TC_CCR = AT91C_TC_SWTRG; - - /* If skip == -1, forward prng time based */ - if(skip == -1) { - i = (legic_prng_bc+SIM_SHIFT)/SIM_DIVISOR; /* Calculate Cycles based on timer */ - i -= legic_prng_count(); /* substract cycles of finished frames */ - i -= count; /* substract current frame length, rewidn to bedinning */ - legic_prng_forward(i); - } else { - legic_prng_forward(skip); - } - - /* Write Time Data into LOG */ - uint8_t *BigBuf = BigBuf_get_addr(); - if(count == 6) { i = -1; } else { i = legic_read_count; } - BigBuf[OFFSET_LOG+128+i] = legic_prng_count(); - BigBuf[OFFSET_LOG+256+i*4] = (legic_prng_bc >> 0) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+1] = (legic_prng_bc >> 8) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+2] = (legic_prng_bc >>16) & 0xff; - BigBuf[OFFSET_LOG+256+i*4+3] = (legic_prng_bc >>24) & 0xff; - BigBuf[OFFSET_LOG+384+i] = count; - - /* Generate KeyStream */ - for(i=0; iPIO_CODR = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; - - /* Use time to crypt frame */ - if(crypt) { - legic_prng_forward(2); /* TAG_TIME_WAIT -> shift by 2 */ - int i; int key = 0; - for(i=0; iTC_CV < (TAG_FRAME_WAIT - 30)) ; - - int i; - for(i=0; iTC_CV + TAG_BIT_PERIOD; - int bit = response & 1; - response = response >> 1; - if(bit) { - AT91C_BASE_PIOA->PIO_SODR = GPIO_SSC_DOUT; - } else { - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; - } - while(timer->TC_CV < nextbit) ; - } - AT91C_BASE_PIOA->PIO_CODR = GPIO_SSC_DOUT; -} - -static void frame_append_bit(struct legic_frame * const f, int bit) -{ - if(f->bits >= 31) { - return; /* Overflow, won't happen */ - } - f->data |= (bit<bits); - f->bits++; -} - -static void frame_clean(struct legic_frame * const f) -{ - f->data = 0; - f->bits = 0; -} - -/* Handle (whether to respond) a frame in tag mode */ -static void frame_handle_tag(struct legic_frame const * const f) -{ - uint8_t *BigBuf = BigBuf_get_addr(); - - /* First Part of Handshake (IV) */ - if(f->bits == 7) { - if(f->data == SESSION_IV) { - LED_C_ON(); - prng_timer->TC_CCR = AT91C_TC_SWTRG; - legic_prng_init(f->data); - frame_send_tag(0x3d, 6, 1); /* 0x3d^0x26 = 0x1b */ - legic_state = STATE_IV; - legic_read_count = 0; - legic_prng_bc = 0; - legic_prng_iv = f->data; - - /* TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - while(timer->TC_CV < 280); - return; - } else if((prng_timer->TC_CV % 50) > 40) { - legic_prng_init(f->data); - frame_send_tag(0x3d, 6, 1); - SpinDelay(20); - return; - } - } - - /* 0x19==??? */ - if(legic_state == STATE_IV) { - if((f->bits == 6) && (f->data == (0x19 ^ get_key_stream(1, 6)))) { - legic_state = STATE_CON; - - /* TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - while(timer->TC_CV < 200); - return; - } else { - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("0x19 - Frame: %03.3x", f->data); - return; - } - } - - /* Read */ - if(f->bits == 11) { - if(legic_state == STATE_CON) { - int key = get_key_stream(-1, 11); //legic_phase_drift, 11); - int addr = f->data ^ key; addr = addr >> 1; - int data = BigBuf[addr]; - int hash = calc_crc4(addr, data, 11) << 8; - BigBuf[OFFSET_LOG+legic_read_count] = (uint8_t)addr; - legic_read_count++; - - //Dbprintf("Data:%03.3x, key:%03.3x, addr: %03.3x, read_c:%u", f->data, key, addr, read_c); - legic_prng_forward(legic_reqresp_drift); - - frame_send_tag(hash | data, 12, 1); - - /* SHORT TIMEOUT */ - timer->TC_CCR = AT91C_TC_SWTRG; - while(timer->TC_CV > 1); - legic_prng_forward(legic_frame_drift); - while(timer->TC_CV < 180); - return; - } - } - - /* Write */ - if(f->bits == 23) { - int key = get_key_stream(-1, 23); //legic_frame_drift, 23); - int addr = f->data ^ key; addr = addr >> 1; addr = addr & 0x3ff; - int data = f->data ^ key; data = data >> 11; data = data & 0xff; - - /* write command */ - legic_state = STATE_DISCON; - LED_C_OFF(); - Dbprintf("write - addr: %x, data: %x", addr, data); - return; - } - - if(legic_state != STATE_DISCON) { - Dbprintf("Unexpected: sz:%u, Data:%03.3x, State:%u, Count:%u", f->bits, f->data, legic_state, legic_read_count); - int i; - Dbprintf("IV: %03.3x", legic_prng_iv); - for(i = 0; iPIO_ODR = GPIO_SSC_DIN; - AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DIN; - - setup_timer(); - crc_init(&legic_crc, 4, 0x19 >> 1, 0x5, 0); - - int old_level = 0; - int active = 0; - legic_state = STATE_DISCON; - - LED_B_ON(); - DbpString("Starting Legic emulator, press button to end"); - while(!BUTTON_PRESS()) { - int level = !!(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN); - int time = timer->TC_CV; - - if(level != old_level) { - if(level == 1) { - timer->TC_CCR = AT91C_TC_CLKEN | AT91C_TC_SWTRG; - if(FUZZ_EQUAL(time, RWD_TIME_1, RWD_TIME_FUZZ)) { - /* 1 bit */ - emit(1); - active = 1; - LED_A_ON(); - } else if(FUZZ_EQUAL(time, RWD_TIME_0, RWD_TIME_FUZZ)) { - /* 0 bit */ - emit(0); - active = 1; - LED_A_ON(); - } else if(active) { - /* invalid */ - emit(-1); - active = 0; - LED_A_OFF(); - } - } - } - - if(time >= (RWD_TIME_1+RWD_TIME_FUZZ) && active) { - /* Frame end */ - emit(-1); - active = 0; - LED_A_OFF(); - } - - if(time >= (20*RWD_TIME_1) && (timer->TC_SR & AT91C_TC_CLKSTA)) { - timer->TC_CCR = AT91C_TC_CLKDIS; - } - - old_level = level; - WDT_HIT(); - } - DbpString("Stopped"); - LED_B_OFF(); - LED_A_OFF(); - LED_C_OFF(); -} - diff --git a/armsrc/legicrf.h b/armsrc/legicrf.h index 46459856..9f8cf457 100644 --- a/armsrc/legicrf.h +++ b/armsrc/legicrf.h @@ -1,5 +1,6 @@ //----------------------------------------------------------------------------- // (c) 2009 Henryk Plötz +// 2018 AntiCat // // This code is licensed to you under the terms of the GNU GPL, version 2 or, // at your option, any later version. See the LICENSE.txt file for the text of @@ -11,7 +12,6 @@ #ifndef __LEGICRF_H #define __LEGICRF_H -extern void LegicRfSimulate(int phase, int frame, int reqresp); extern void LegicRfReader(int bytes, int offset); extern void LegicRfWriter(int bytes, int offset); diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c new file mode 100644 index 00000000..07a0a62d --- /dev/null +++ b/armsrc/legicrfsim.c @@ -0,0 +1,468 @@ +//----------------------------------------------------------------------------- +// (c) 2009 Henryk Plötz +// 2016 Iceman +// 2018 AntiCat +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// LEGIC RF simulation code +//----------------------------------------------------------------------------- + +#include "proxmark3.h" +#include "apps.h" +#include "util.h" +#include "string.h" + +#include "legicrfsim.h" +#include "legic_prng.h" +#include "legic.h" +#include "crc.h" + +static uint8_t* legic_mem; /* card memory, used for sim */ +static legic_card_select_t card;/* metadata of currently selected card */ +static crc_t legic_crc; + +//----------------------------------------------------------------------------- +// Frame timing and pseudorandom number generator +// +// The Prng is forwarded every 99.1us (TAG_BIT_PERIOD), except when the reader is +// transmitting. In that case the prng has to be forwarded every bit transmitted: +// - 31.3us for a 0 (RWD_TIME_0) +// - 99.1us for a 1 (RWD_TIME_1) +// +// The data dependent timing makes writing comprehensible code significantly +// harder. The current aproach forwards the prng data based if there is data on +// air and time based, using GetCountSspClk(), during computational and wait +// periodes. SSP Clock is clocked by the FPGA at 212 kHz (subcarrier frequency). +// +// To not have the necessity to calculate/guess exection time dependend timeouts +// tx_frame and rx_frame use a shared timestamp to coordinate tx and rx timeslots. +//----------------------------------------------------------------------------- + +static uint32_t last_frame_end; /* ts of last bit of previews rx or tx frame */ + +#define TAG_FRAME_WAIT 70 /* 330us from READER frame end to TAG frame start */ +#define TAG_ACK_WAIT 758 /* 3.57ms from READER frame end to TAG write ACK */ +#define TAG_BIT_PERIOD 21 /* 99.1us */ + +#define RWD_TIME_PAUSE 4 /* 18.9us */ +#define RWD_TIME_1 21 /* RWD_TIME_PAUSE 18.9us off + 80.2us on = 99.1us */ +#define RWD_TIME_0 13 /* RWD_TIME_PAUSE 18.9us off + 42.4us on = 61.3us */ +#define RWD_CMD_TIMEOUT 40 /* 40 * 99.1us (arbitrary value) */ +#define RWD_MIN_FRAME_LEN 6 /* Shortest frame is 6 bits */ +#define RWD_MAX_FRAME_LEN 23 /* Longest frame is 23 bits */ + +#define RWD_PULSE 1 /* Pulse is signaled with GPIO_SSC_DIN high */ +#define RWD_PAUSE 0 /* Pause is signaled with GPIO_SSC_DIN low */ + +//----------------------------------------------------------------------------- +// Demodulation +//----------------------------------------------------------------------------- + +// Returns true if a pulse/pause is received within timeout +static inline bool wait_for(bool value, const uint32_t timeout) { + while((bool)(AT91C_BASE_PIOA->PIO_PDSR & GPIO_SSC_DIN) != value) { + if(GetCountSspClk() > timeout) { + return false; + } + } + return true; +} + +// Returns a demedulated bit or -1 on code violation +// +// rx_bit decodes bits using a thresholds. rx_bit has to be called by as soon as +// a frame starts (first pause is received). rx_bit checks for a pause up to +// 18.9us followed by a pulse of 80.2us or 42.4us: +// - A bit length <18.9us is a code violation +// - A bit length >80.2us is a 1 +// - A bit length <80.2us is a 0 +// - A bit length >148.6us is a code violation +static inline int8_t rx_bit() { + // backup ts for threshold calculation + uint32_t bit_start = last_frame_end; + + // wait for pause to end + if(!wait_for(RWD_PULSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // wait for next pause + if(!wait_for(RWD_PAUSE, bit_start + RWD_TIME_1*3/2)) { + return -1; + } + + // update bit and frame end + last_frame_end = GetCountSspClk(); + + // check for code violation (bit to short) + if(last_frame_end - bit_start < RWD_TIME_PAUSE) { + return -1; + } + + // apply threshold (average of RWD_TIME_0 and ) + return (last_frame_end - bit_start > (RWD_TIME_0 + RWD_TIME_1) / 2); +} + +//----------------------------------------------------------------------------- +// Modulation +// +// LEGIC RF uses a very basic load modulation from card to reader: +// - Subcarrier on for a 1 +// - Subcarrier off for for a 0 +// +// The 212kHz subcarrier is generated by the FPGA as well as a mathcing ssp clk. +// Each bit is transfered in a 99.1us slot and the first timeslot starts 330us +// after the final 20us pause generated by the reader. +//----------------------------------------------------------------------------- + +// Transmits a bit +// +// Note: The Subcarrier is not disabled during bits to prevent glitches. This is +// not mandatory but results in a cleaner signal. tx_frame will disable +// the subcarrier when the frame is done. +static inline void tx_bit(bool bit) { + LED_C_ON(); + + if(bit) { + // modulate subcarrier + HIGH(GPIO_SSC_DOUT); + } else { + // do not modulate subcarrier + LOW(GPIO_SSC_DOUT); + } + + // wait for tx timeslot to end + last_frame_end += TAG_BIT_PERIOD; + while(GetCountSspClk() < last_frame_end) { }; + LED_C_OFF(); +} + +//----------------------------------------------------------------------------- +// Frame Handling +// +// The LEGIC RF protocol from reader to card does not include explicit frame +// start/stop information or length information. The tag detects end of frame +// trough an extended pulse (>99.1us) without a pause. +// In reverse direction (card to reader) the number of bites is well known +// and depends only the command received (IV, ACK, READ or WRITE). +//----------------------------------------------------------------------------- + +static void tx_frame(uint32_t frame, uint8_t len) { + // wait for next tx timeslot + last_frame_end += TAG_FRAME_WAIT; + legic_prng_forward(TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit frame, MSB first + for(uint8_t i = 0; i < len; ++i) { + bool bit = (frame >> i) & 0x01; + tx_bit(bit ^ legic_prng_get_bit()); + legic_prng_forward(1); + }; + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +static void tx_ack() { + // wait for ack timeslot + last_frame_end += TAG_ACK_WAIT; + legic_prng_forward(TAG_ACK_WAIT/TAG_BIT_PERIOD - 1); + while(GetCountSspClk() < last_frame_end) { }; + + // transmit ack (ack is not encrypted) + tx_bit(true); + legic_prng_forward(1); + + // disable subcarrier + LOW(GPIO_SSC_DOUT); +} + +// Returns a demedulated frame or -1 on code violation +// +// Since TX to RX delay is arbitrary rx_frame has to: +// - detect start of frame (first pause) +// - forward prng based on ts/TAG_BIT_PERIOD +// - receive the frame +// - detect end of frame (last pause) +static int32_t rx_frame(uint8_t *len) { + int32_t frame = 0; + + // add 2 SSP clock cycles (1 for tx and 1 for rx pipeline delay) + // those will be substracted at the end of the rx phase + last_frame_end -= 2; + + // wait for first pause (start of frame) + for(uint8_t i = 0; true; ++i) { + // increment prng every TAG_BIT_PERIOD + last_frame_end += TAG_BIT_PERIOD; + legic_prng_forward(1); + + // if start of frame was received exit delay loop + if(wait_for(RWD_PAUSE, last_frame_end)) { + last_frame_end = GetCountSspClk(); + break; + } + + // check for code violation + if(i > RWD_CMD_TIMEOUT) { + return -1; + } + } + + // receive frame + for(*len = 0; true; ++(*len)) { + // receive next bit + LED_D_ON(); + int8_t bit = rx_bit(); + LED_D_OFF(); + + // check for code violation and to short / long frame + if((bit < 0) && ((*len < RWD_MIN_FRAME_LEN) || (*len > RWD_MAX_FRAME_LEN))) { + return -1; + } + + // check for code violation caused by end of frame + if(bit < 0) { + break; + } + + // append bit + frame |= (bit ^ legic_prng_get_bit()) << (*len); + legic_prng_forward(1); + } + + // rx_bit sets coordination timestamp to start of pause, append pause duration + // and substract 2 SSP clock cycles (1 for rx and 1 for tx pipeline delay) to + // obtain exact end of frame. + last_frame_end += RWD_TIME_PAUSE - 2; + + return frame; +} + +//----------------------------------------------------------------------------- +// Legic Simulator +//----------------------------------------------------------------------------- + +static int32_t init_card(uint8_t cardtype, legic_card_select_t *p_card) { + p_card->tagtype = cardtype; + + switch(p_card->tagtype) { + case 0: + p_card->cmdsize = 6; + p_card->addrsize = 5; + p_card->cardsize = 22; + break; + case 1: + p_card->cmdsize = 9; + p_card->addrsize = 8; + p_card->cardsize = 256; + break; + case 2: + p_card->cmdsize = 11; + p_card->addrsize = 10; + p_card->cardsize = 1024; + break; + default: + p_card->cmdsize = 0; + p_card->addrsize = 0; + p_card->cardsize = 0; + return 2; + } + return 0; +} + +static void init_tag() { + // configure FPGA + FpgaDownloadAndGo(FPGA_BITSTREAM_HF); + FpgaWriteConfWord(FPGA_MAJOR_MODE_HF_SIMULATOR + | FPGA_HF_SIMULATOR_MODULATE_212K); + SetAdcMuxFor(GPIO_MUXSEL_HIPKD); + + // configure SSC with defaults + FpgaSetupSsc(); + + // first pull output to low to prevent glitches then re-claim GPIO_SSC_DOUT + LOW(GPIO_SSC_DOUT); + AT91C_BASE_PIOA->PIO_OER = GPIO_SSC_DOUT; + AT91C_BASE_PIOA->PIO_PER = GPIO_SSC_DOUT; + + // reserve a cardmem, meaning we can use the tracelog function in bigbuff easier. + legic_mem = BigBuf_get_addr(); + + // init crc calculator + crc_init(&legic_crc, 4, 0x19 >> 1, 0x05, 0); + + // start 212kHz timer (running from SSP Clock) + StartCountSspClk(); +} + +// Setup reader to card connection +// +// The setup consists of a three way handshake: +// - Receive initialisation vector 7 bits +// - Transmit card type 6 bits +// - Receive Acknowledge 6 bits +static int32_t setup_phase(legic_card_select_t *p_card) { + uint8_t len = 0; + + // init coordination timestamp + last_frame_end = GetCountSspClk(); + + // reset prng + legic_prng_init(0); + + // wait for iv + int32_t iv = rx_frame(&len); + if((len != 7) || (iv < 0)) { + return -1; + } + + // configure prng + legic_prng_init(iv); + + // reply with card type + switch(p_card->tagtype) { + case 0: + tx_frame(0x0D, 6); + break; + case 1: + tx_frame(0x1D, 6); + break; + case 2: + tx_frame(0x3D, 6); + break; + } + + // wait for ack + int32_t ack = rx_frame(&len); + if((len != 6) || (ack < 0)) { + return -1; + } + + // validate data + switch(p_card->tagtype) { + case 0: + if(ack != 0x19) return -1; + break; + case 1: + if(ack != 0x39) return -1; + break; + case 2: + if(ack != 0x39) return -1; + break; + } + + // During rx the prng is clocked using the variable reader period. + // Since rx_frame detects end of frame by detecting a code violation, + // the prng is off by one bit period after each rx phase. Hence, tx + // code advances the prng by (TAG_FRAME_WAIT/TAG_BIT_PERIOD - 1). + // This is not possible for back to back rx, so this quirk reduces + // the gap by one period. + last_frame_end += TAG_BIT_PERIOD; + + return 0; +} + +static uint8_t calc_crc4(uint16_t cmd, uint8_t cmd_sz, uint8_t value) { + crc_clear(&legic_crc); + crc_update(&legic_crc, (value << cmd_sz) | cmd, 8 + cmd_sz); + return crc_finish(&legic_crc); +} + +static int32_t connected_phase(legic_card_select_t *p_card) { + uint8_t len = 0; + + // wait for command + int32_t cmd = rx_frame(&len); + if(cmd < 0) { + return -1; + } + + // check if command is LEGIC_READ + if(len == p_card->cmdsize) { + // prepare data + uint8_t byte = legic_mem[cmd >> 1]; + uint8_t crc = calc_crc4(cmd, p_card->cmdsize, byte); + + // transmit data + tx_frame((crc << 8) | byte, 12); + + return 0; + } + + // check if command is LEGIC_WRITE + if(len == p_card->cmdsize + 8 + 4) { + // decode data + uint16_t mask = (1 << p_card->addrsize) - 1; + uint16_t addr = (cmd >> 1) & mask; + uint8_t byte = (cmd >> p_card->cmdsize) & 0xff; + uint8_t crc = (cmd >> (p_card->cmdsize + 8)) & 0xf; + + // check received against calculated crc + uint8_t calc_crc = calc_crc4(addr << 1, p_card->cmdsize, byte); + if(calc_crc != crc) { + Dbprintf("!!! crc mismatch: %x != %x !!!", calc_crc, crc); + return -1; + } + + // store data + legic_mem[addr] = byte; + + // transmit ack + tx_ack(); + + return 0; + } + + return -1; +} + +//----------------------------------------------------------------------------- +// Command Line Interface +// +// Only this function is public / called from appmain.c +//----------------------------------------------------------------------------- + +void LegicRfSimulate(uint8_t cardtype) { + // configure ARM and FPGA + init_tag(); + + // verify command line input + if(init_card(cardtype, &card) != 0) { + DbpString("Unknown tagtype."); + goto OUT; + } + + LED_A_ON(); + DbpString("Starting Legic emulator, press button to end"); + while(!BUTTON_PRESS()) { + WDT_HIT(); + + // wait for carrier, restart after timeout + if(!wait_for(RWD_PULSE, GetCountSspClk() + TAG_BIT_PERIOD)) { + continue; + } + + // wait for connection, restart on error + if(setup_phase(&card)) { + continue; + } + + // conection is established, process commands until one fails + while(!connected_phase(&card)) { + WDT_HIT(); + } + } + +OUT: + DbpString("Stopped"); + FpgaWriteConfWord(FPGA_MAJOR_MODE_OFF); + LED_A_OFF(); + LED_C_OFF(); + LED_D_OFF(); + StopTicks(); +} diff --git a/armsrc/legicrfsim.h b/armsrc/legicrfsim.h new file mode 100644 index 00000000..c1c8a86e --- /dev/null +++ b/armsrc/legicrfsim.h @@ -0,0 +1,19 @@ +//----------------------------------------------------------------------------- +// (c) 2009 Henryk Plötz +// 2018 AntiCat +// +// This code is licensed to you under the terms of the GNU GPL, version 2 or, +// at your option, any later version. See the LICENSE.txt file for the text of +// the license. +//----------------------------------------------------------------------------- +// LEGIC RF emulation public interface +//----------------------------------------------------------------------------- + +#ifndef __LEGICRFSIM_H +#define __LEGICRFSIM_H + +#include "proxmark3.h" + +extern void LegicRfSimulate(uint8_t tagtype); + +#endif /* __LEGICRFSIM_H */ diff --git a/client/cmdhflegic.c b/client/cmdhflegic.c index 691e1978..8fbd4578 100644 --- a/client/cmdhflegic.c +++ b/client/cmdhflegic.c @@ -28,7 +28,7 @@ static command_t CommandTable[] = {"reader", CmdLegicRFRead, 0, "[offset [length]] -- read bytes from a LEGIC card"}, {"save", CmdLegicSave, 0, " [] -- Store samples"}, {"load", CmdLegicLoad, 0, " -- Restore samples"}, - {"sim", CmdLegicRfSim, 0, "[phase drift [frame drift [req/resp drift]]] Start tag simulator (use after load or read)"}, + {"sim", CmdLegicRfSim, 0, "[tagtype, 0:MIM22, 1:MIM256, 2:MIM1024] Start tag simulator (use after load or read)"}, {"write", CmdLegicRfWrite,0, " -- Write sample buffer (user after load or read)"}, {"fill", CmdLegicRfFill, 0, " -- Fill/Write tag with constant value"}, {NULL, NULL, 0, NULL} @@ -320,10 +320,8 @@ int CmdLegicSave(const char *Cmd) int CmdLegicRfSim(const char *Cmd) { UsbCommand c={CMD_SIMULATE_TAG_LEGIC_RF}; - c.arg[0] = 6; - c.arg[1] = 3; - c.arg[2] = 0; - sscanf(Cmd, " %" SCNi64 " %" SCNi64 " %" SCNi64, &c.arg[0], &c.arg[1], &c.arg[2]); + c.arg[0] = 1; + sscanf(Cmd, " %" SCNi64, &c.arg[0]); SendCommand(&c); return 0; } diff --git a/fpga/fpga_hf.bit b/fpga/fpga_hf.bit index 939ba93a54ce00e9425a1156d3900f1af58c4c8e..179e87eed22bc326f37e33f6925116916701c6bf 100644 GIT binary patch literal 42175 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 literal 42175 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 diff --git a/fpga/hi_simulate.v b/fpga/hi_simulate.v index 0768c29d..78650c4a 100644 --- a/fpga/hi_simulate.v +++ b/fpga/hi_simulate.v @@ -51,38 +51,29 @@ begin end -// Divide 13.56 MHz by 32 to produce the SSP_CLK -// The register is bigger to allow higher division factors of up to /128 +// Divide 13.56 MHz to produce various frequencies for SSP_CLK +// and modulation. 11 bits allow for factors of up to /128. reg [10:0] ssp_clk_divider; always @(posedge adc_clk) ssp_clk_divider <= (ssp_clk_divider + 1); reg ssp_clk; -reg ssp_frame; + always @(negedge adc_clk) begin - //If we're in 101, we only need a new bit every 8th carrier bit (53Hz). Otherwise, get next bit at 424Khz if(mod_type == 3'b101) - begin - if(ssp_clk_divider[7:0] == 8'b00000000) - ssp_clk <= 1'b0; - if(ssp_clk_divider[7:0] == 8'b10000000) - ssp_clk <= 1'b1; - - end + // Get bit every at 53KHz (every 8th carrier bit of 424kHz) + ssp_clk <= ssp_clk_divider[7]; + else if(mod_type == 3'b010) + // Get next bit at 212kHz + ssp_clk <= ssp_clk_divider[5]; else - begin - if(ssp_clk_divider[4:0] == 5'd0)//[4:0] == 5'b00000) - ssp_clk <= 1'b1; - if(ssp_clk_divider[4:0] == 5'd16) //[4:0] == 5'b10000) - ssp_clk <= 1'b0; - end + // Get next bit at 424Khz + ssp_clk <= ssp_clk_divider[4]; end -//assign ssp_clk = ssp_clk_divider[4]; - // Divide SSP_CLK by 8 to produce the byte framing signal; the phase of // this is arbitrary, because it's just a bitstream. // One nasty issue, though: I can't make it work with both rx and tx at @@ -96,19 +87,19 @@ always @(negedge ssp_clk) ssp_frame_divider_from_arm <= (ssp_frame_divider_from_arm + 1); - +reg ssp_frame; always @(ssp_frame_divider_to_arm or ssp_frame_divider_from_arm or mod_type) if(mod_type == 3'b000) // not modulating, so listening, to ARM ssp_frame = (ssp_frame_divider_to_arm == 3'b000); else - ssp_frame = (ssp_frame_divider_from_arm == 3'b000); + ssp_frame = (ssp_frame_divider_from_arm == 3'b000); // Synchronize up the after-hysteresis signal, to produce DIN. reg ssp_din; always @(posedge ssp_clk) ssp_din = after_hysteresis; -// Modulating carrier frequency is fc/16, reuse ssp_clk divider for that +// Modulating carrier frequency is fc/64 (212kHz) to fc/16 (848kHz). Reuse ssp_clk divider for that. reg modulating_carrier; always @(mod_type or ssp_clk or ssp_dout) if(mod_type == 3'b000) @@ -116,9 +107,9 @@ always @(mod_type or ssp_clk or ssp_dout) else if(mod_type == 3'b001) modulating_carrier <= ssp_dout ^ ssp_clk_divider[3]; // XOR means BPSK else if(mod_type == 3'b010) - modulating_carrier <= ssp_dout & ssp_clk_divider[5]; // switch 212kHz subcarrier on/off + modulating_carrier <= ssp_dout & ssp_clk_divider[5]; // switch 212kHz subcarrier on/off else if(mod_type == 3'b100 || mod_type == 3'b101) - modulating_carrier <= ssp_dout & ssp_clk_divider[4]; // switch 424kHz modulation on/off + modulating_carrier <= ssp_dout & ssp_clk_divider[4]; // switch 424kHz modulation on/off else modulating_carrier <= 1'b0; // yet unused @@ -133,9 +124,6 @@ assign pwr_oe4 = modulating_carrier; // This one is always on, so that we can watch the carrier. assign pwr_oe3 = 1'b0; -assign dbg = modulating_carrier; -//reg dbg; -//always @(ssp_dout) -// dbg <= ssp_dout; +assign dbg = ssp_din; endmodule From 53edb044c0dfe82fd9d0a0e0d3444a51ebe358f4 Mon Sep 17 00:00:00 2001 From: wllm-rbnt Date: Mon, 10 Sep 2018 18:19:31 +0200 Subject: [PATCH 4/4] Allow clean Legic simulation exit without button press (#668) --- armsrc/legicrfsim.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/armsrc/legicrfsim.c b/armsrc/legicrfsim.c index 07a0a62d..34633f36 100644 --- a/armsrc/legicrfsim.c +++ b/armsrc/legicrfsim.c @@ -19,6 +19,7 @@ #include "legic_prng.h" #include "legic.h" #include "crc.h" +#include "usb_cdc.h" // for usb_poll_validate_length static uint8_t* legic_mem; /* card memory, used for sim */ static legic_card_select_t card;/* metadata of currently selected card */ @@ -439,7 +440,7 @@ void LegicRfSimulate(uint8_t cardtype) { LED_A_ON(); DbpString("Starting Legic emulator, press button to end"); - while(!BUTTON_PRESS()) { + while(!BUTTON_PRESS() && !usb_poll_validate_length()) { WDT_HIT(); // wait for carrier, restart after timeout