add hexconv.c, a tool for converting hex strings to signed decimals
authorColin Patrick Mccabe <cmccabe@alumni.cmu.edu>
Thu, 6 Nov 2014 04:12:48 +0000 (20:12 -0800)
committerColin Patrick Mccabe <cmccabe@alumni.cmu.edu>
Thu, 6 Nov 2014 04:12:48 +0000 (20:12 -0800)
Signed-off-by: Colin McCabe <cmccabe@alumni.cmu.edu>

.gitignore
Makefile
hexconv.c [new file with mode: 0644]

index 6c30d1d..f0b2e2e 100644 (file)
@@ -11,6 +11,7 @@ simple_time
 vimstart
 show_default_sockopts
 print-code-points
+hexconv
 
 #
 # Normal rules
index 20180f3..b6bab15 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,6 @@
 CFLAGS=-Wall -O2
 
-all:           bytor errno_speak show_default_sockopts simple_time vimstart
+all:           bytor errno_speak show_default_sockopts simple_time vimstart hexconv
 
 bytor:
        go build bytor.go
@@ -13,5 +13,7 @@ simple_time:  simple_time.o
 
 vimstart:      vimstart.o
 
+hexconv:       hexconv.o
+
 clean:
-       rm -rf errno_speak show_default_sockopts simple_time vimstart *.o
+       rm -rf errno_speak show_default_sockopts simple_time vimstart hexconv *.o
diff --git a/hexconv.c b/hexconv.c
new file mode 100644 (file)
index 0000000..ba4dba3
--- /dev/null
+++ b/hexconv.c
@@ -0,0 +1,160 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+/*
+ * This program is useful for converting between hex strings and signed
+ * integers.  It can handle numbers of arbitrary bit sizes (provided they're
+ * between 2 and 63 inclusive.)
+ *
+ * Colin McCabe
+ */
+
+#define EXIT_WITH_NUMBER 2
+
+static int run_unit_tests(void);
+
+static void usage(const char *argv0)
+{
+    fprintf(stderr, "%s: convert a hex number to its decimal "
+            "representation.\n", argv0);
+    fprintf(stderr, "\n");
+    fprintf(stderr, "-b [bitsize]: bit size to assume (defaults to 32).\n");
+    fprintf(stderr, "-h: this help message.\n");
+    fprintf(stderr, "-n [number]: number to convert.  Must not be 0.\n");
+    fprintf(stderr, "-s: assume signed (default is unsigned).\n");
+    fprintf(stderr, "-u: run unit tests.\n");
+}
+
+static int process(char **argv, long long *result)
+{
+    int c, i, argc = 0, bitsize = 32, is_signed = 0;
+    long long number = 0;
+    char *endptr;
+
+    for (i = 0; argv[i]; i++) {
+        argc++;
+    }
+    optind = 1;
+    while ((c = getopt(argc, argv, "b:hn:su")) != -1) {
+        switch (c) {
+        case 'b':
+            bitsize = atoi(optarg);
+            break;
+        case 'h':
+            usage(argv[0]);
+            return EXIT_SUCCESS;
+        case 'n':
+            if (strncmp(optarg, "0x", 2) != 0) {
+                fprintf(stderr, "The number you pass must start with 0x.\n");
+                return EXIT_FAILURE;
+            }
+            endptr = NULL;
+            errno = 0;
+            number = strtoull(optarg + 2, &endptr, 16);
+            if (errno) {
+                int err = errno;
+                fprintf(stderr, "strtoull failed: error %d (%s)\n",
+                        err, strerror(err));
+                return EXIT_FAILURE;
+            }
+            if (*endptr != '\0') {
+                fprintf(stderr, "Garbage at end of string, beginning "
+                        "with %s\n", endptr);
+                return EXIT_FAILURE;
+            }
+            break;
+        case 's':
+            is_signed = 1;
+            break;
+        case 'u':
+            return run_unit_tests();
+        default:
+            fprintf (stderr, "getopt error.\n");
+            return EXIT_FAILURE;
+        }
+    }
+    if ((bitsize <= 1) || (bitsize >= 64)) {
+        fprintf(stderr, "Invalid bitsize of %d.  Bitsize must be "
+                "between 2 and 63, inclusive.\n", bitsize);
+        return EXIT_FAILURE;
+    }
+    if (number == 0) {
+        fprintf(stderr, "You must specify a non-zero number to convert.  -h "
+                "for help.\n");
+        return EXIT_FAILURE;
+    }
+    if (number > (1LL << bitsize)) {
+        fprintf(stderr, "Number %lld does not fit in %d bits.\n",
+                number, bitsize);
+        return EXIT_FAILURE;
+    }
+    // Mask off unused bits.
+    if (is_signed) {
+        unsigned long long highest_neg = (1LLU << (bitsize - 1));
+        if ((unsigned long long)number >= highest_neg) {
+            unsigned long long full = (1LLU << bitsize);
+            number -= full;
+        }
+    } else {
+        number &= ((1LLU << bitsize) - 1LLU);
+    }
+    *result = number;
+    return EXIT_WITH_NUMBER;
+}
+
+static int run_unit_tests(void)
+{
+    long long result;
+    char *help_args[] = { "hexconv", "-h", NULL };
+    char *simple_args[] = { "hexconv", "-n", "0xff", NULL };
+    char *invalid_1[] = { "hexconv", "-n", "123", NULL };
+    char *invalid_2[] = { "hexconv", "-n", "0x123", "-b", "65", NULL };
+    char *invalid_3[] = { "hexconv", "-n", "0xffff", "-b", "8", NULL };
+    char *neg_1[] = { "hexconv", "-n", "0x80", "-b", "8", "-s", NULL };
+    char *neg_2[] = { "hexconv", "-n", "0xff", "-b", "8", "-s", NULL };
+    char *neg_3[] = { "hexconv", "-n", "0xffff", "-b", "16", "-s", NULL };
+    char *neg_4[] = { "hexconv", "-n", "0xfffffe", "-b", "24", "-s", NULL };
+
+    fprintf(stderr, "*** testing -h ***\n");
+    if (EXIT_SUCCESS != process(help_args, NULL)) abort();
+    fprintf(stderr, "*** testing converting 0xff ***\n");
+    if (EXIT_WITH_NUMBER != process(simple_args, &result)) abort();
+    if (result != 255) abort();
+    fprintf(stderr, "*** testing invalid input 123 ***\n");
+    if (EXIT_FAILURE != process(invalid_1, &result)) abort();
+    fprintf(stderr, "*** testing invalid bitsize input ***\n");
+    if (EXIT_FAILURE != process(invalid_2, &result)) abort();
+    fprintf(stderr, "*** testing too-big input ***\n");
+    if (EXIT_FAILURE != process(invalid_3, &result)) abort();
+    fprintf(stderr, "*** testing converting 0x80 with in signed mode ***\n");
+    if (EXIT_WITH_NUMBER != process(neg_1, &result)) abort();
+    if (result != -128) abort();
+    fprintf(stderr, "*** testing converting 0xff with in signed mode ***\n");
+    if (EXIT_WITH_NUMBER != process(neg_2, &result)) abort();
+    if (result != -1) abort();
+    fprintf(stderr, "*** testing converting 0xffff with in signed mode ***\n");
+    if (EXIT_WITH_NUMBER != process(neg_3, &result)) abort();
+    if (result != -1) abort();
+    fprintf(stderr, "*** testing converting 0xfffffe with in signed mode ***\n");
+    if (EXIT_WITH_NUMBER != process(neg_4, &result)) abort();
+    if (result != -2) abort();
+    return EXIT_SUCCESS;
+}
+
+int main(int argc, char **argv)
+{
+    long long result = argc; // avoid annoying "unused variable" warning
+    int ret = process(argv, &result);
+    switch (ret) {
+    case EXIT_SUCCESS:
+        return EXIT_SUCCESS;
+    case EXIT_WITH_NUMBER:
+        printf("%lld\n", result);
+        return EXIT_SUCCESS;
+    default:
+        return EXIT_FAILURE;
+    }
+}