Search code examples
carrayspointersbit-manipulation128-bit

Getting a 128 bits integer from command line


I'm trying to cast an unsigned long long key to do the Tiny Encryption Algorithm algorithm.

#include <stdio.h>
#include <stdlib.h>

int main(int argc, char** argv){
    unsigned int key[4] = { 0 };
    *key = strtoll(argv[1], NULL, 10);
    printf("%s key = %llu\n", argv[0], key);
    return 0;
}

Here is my input :

./a.out 9223372036854775700

Here is the output :

./a.out key = 140723741574976

So I'm passing a 128 bit key in argv[1]. Shouldn't it be cast properly in memory into the unsigned int array?

So, I'm trying to figure out why this is the output of my program. Does this have something to do with endianness?


Solution

  • Take a step back, and look at what you are trying to implement. The Tiny Encryption Algorithm does not work on an 128-bit integer, but on a 128-bit key; the key is composed of four 32-bit unsigned integers.

    What you actually need, is a way to parse a decimal (or hexadecimal, or some other base) 128-bit unsigned integer from a string to four 32-bit unsigned integer elements.

    I suggest writing a multiply-add function, which takes the quad-32-bit value, multiplies it by a 32-bit constant, and adds another 32-bit constant:

    #include <stdint.h>
    
    uint32_t muladd128(uint32_t quad[4], const uint32_t mul, const uint32_t add)
    {
        uint64_t  temp = 0;
    
        temp = (uint64_t)quad[3] * (uint64_t)mul + add;
        quad[3] = temp;
    
        temp = (uint64_t)quad[2] * (uint64_t)mul + (temp >> 32);
        quad[2] = temp;
    
        temp = (uint64_t)quad[1] * (uint64_t)mul + (temp >> 32);
        quad[1] = temp;
    
        temp = (uint64_t)quad[0] * (uint64_t)mul + (temp >> 32);
        quad[0] = temp;
    
        return temp >> 32;
    }
    

    The above uses most significant first word order. It returns nonzero if the result overflows; in fact, it returns the 32-bit overflow itself.

    With that, it is very easy to parse a string describing a nonnegative 128-bit integer in binary, octal, decimal, or hexadecimal:

    #include <stdlib.h>
    #include <stdint.h>
    #include <string.h>
    #include <stdio.h>
    #include <errno.h>
    
    static void clear128(uint32_t quad[4])
    {
        quad[0] = quad[1] = quad[2] = quad[3] = 0;
    }
    
    /* muladd128() */
    
    static const char *parse128(uint32_t quad[4], const char *from)
    {
        if (!from) {
            errno = EINVAL;
            return NULL;
        }
    
        while (*from == '\t' || *from == '\n' || *from == '\v' ||
               *from == '\f' || *from == '\r' || *from == ' ')
            from++;
    
        if (from[0] == '0' && (from[1] == 'x' || from[1] == 'X') &&
            ((from[2] >= '0' && from[2] <= '9') ||
             (from[2] >= 'A' && from[2] <= 'F') ||
             (from[2] >= 'a' && from[2] <= 'f'))) {
            /* Hexadecimal */
            from += 2;
            clear128(quad);
    
            while (1)
                if (*from >= '0' && *from <= '9') {
                    if (muladd128(quad, 16, *from - '0')) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                if (*from >= 'A' && *from <= 'F') {
                    if (muladd128(quad, 16, *from - 'A' + 10)) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                if (*from >= 'a' && *from <= 'f') {
                    if (muladd128(quad, 16, *from - 'a' + 10)) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                    return from;
        }
    
        if (from[0] == '0' && (from[1] == 'b' || from[1] == 'B') &&
            (from[2] >= '0' && from[2] <= '1')) {
            /* Binary */
            from += 2;
            clear128(quad);
    
            while (1)
                if (*from >= '0' && *from <= '1') {
                    if (muladd128(quad, 2, *from - '0')) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                    return from;
        }
    
        if (from[0] == '0' &&
            (from[1] >= '0' && from[1] <= '7')) {
            /* Octal */
            from += 1;
            clear128(quad);
    
            while (1)
                if (*from >= '0' && *from <= '7') {
                    if (muladd128(quad, 8, *from - '0')) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                    return from;
        }
    
        if (from[0] >= '0' && from[0] <= '9') {
            /* Decimal */
            clear128(quad);
    
            while (1)
                if (*from >= '0' && *from <= '9') {
                    if (muladd128(quad, 10, *from - '0')) {
                        errno = ERANGE;
                        return NULL;
                    }
                    from++;
                } else
                    return from;
        }
    
        /* Not a recognized number. */
        errno = EINVAL;
        return NULL;
    }
    
    int main(int argc, char *argv[])
    {
        uint32_t key[4];
        int      arg;
    
        for (arg = 1; arg < argc; arg++) {
            const char *end = parse128(key, argv[arg]);
            if (end) {
                if (*end != '\0')
                    printf("%s: 0x%08x%08x%08x%08x (+ \"%s\")\n", argv[arg], key[0], key[1], key[2], key[3], end);
                else
                    printf("%s: 0x%08x%08x%08x%08x\n", argv[arg], key[0], key[1], key[2], key[3]);
                fflush(stdout);
            } else {
                switch (errno) {
                case ERANGE:
                    fprintf(stderr, "%s: Too large.\n", argv[arg]);
                    break;
                case EINVAL:
                    fprintf(stderr, "%s: Not a nonnegative integer in binary, octal, decimal, or hexadecimal notation.\n", argv[arg]);
                    break;
                default:
                    fprintf(stderr, "%s: %s.\n", argv[arg], strerror(errno));
                    break;
                }
            }
        }
    
        return EXIT_SUCCESS;
    }
    

    It is very straightforward to add support for Base64 and Base85, which are sometimes used; or indeed for any base less than 232.

    And, if you think about the above, it was all down to being precise about what you need.