-3

I try to convert hex values into one string and I already read this question but I don't know how to convert it in an dynamic way, like if my function get a hex series of 8 or 9 or more. How can I optimize this snippet with an loop which iterates over the given hex values and convert each hex value (except the first one) followed by a .?

/* 
Input: char[] ="2b06010201010300"
Output: char[]="1.3.6.1.2.1.1.3.0"
*/
void test(char * hex_arr)
{
  char string[sizeof(hex_arr)*2+1];
  string[0] = '1';
  string[1] = '.';
  string[2] = '3';
  string[3] = '.';
  sprintf(&string[4], "%x", hex_arr[1]);
  string[5] = '.';
  sprintf(&string[6], "%x", hex_arr[2]);
  // ...
  printf("Output: %s\n", string);
}

The input char[] represent a SNMP OBJECT IDENTIFIER.

Sam
  • 387
  • 2
  • 9
  • 22
  • 3
    Is the input a string, like `char input[] = "2b 06 01 02 01 01 03 00"`? Is the output one string? And, how does `1.3` arise from `2b`? – ad absurdum Jul 30 '17 at 23:42
  • @DavidBowling The input string is like `unsigned char [] = "2b06010201010300"`, the output is one string. `2b` is specified as `1.3`. – Sam Jul 30 '17 at 23:46
  • 1
    Can you give more examples of input and output? And describe your conversion in more detail! Btw, 2b = 43, so I don't think anyone here understands how you get to 1.3... – Elmar Peise Jul 30 '17 at 23:48
  • So, no delimiters as suggested in your code comments; every hex value is represented by two digits. Why `unsigned char`? And what if the input string does _not_ begin with `2b`? – ad absurdum Jul 30 '17 at 23:49
  • Please clarify: Is your entire input one number? is it grouped in pairs of two? What have you tried so far? – Elmar Peise Jul 30 '17 at 23:51
  • @ElmarPeise No, 06 is a 6, 01 is 1 and so on, the whole array represents different number, not one. 2b06010201010300 is equals to 1.3.6.1.2.1.1.3.0 – Sam Jul 30 '17 at 23:59
  • @DavidBowling There is no reason why I choose unsigned char I corrected it in my question. – Sam Jul 31 '17 at 00:05
  • Still unclear. 1) There is no conversion to decimal, only a removal of leading zeros and added periods? 2) What if input does _not_ begin with `2b` as expected? Is this an input error? Wait, SNMP object identifier has been added; so you intend for the hex values to be converted to decimal values.... – ad absurdum Jul 31 '17 at 00:06
  • @DavidBowling I dont care at the moment if the input not start with 2b. I want, like you say `hex values to be converted to decimal values.` – Sam Jul 31 '17 at 00:10
  • 2
    The BER encoding of ASN.1 OIDs (that SNMP uses) is not limited to one element per noninitial octet as your trivial example is, and in principle not even limited to sizes C (or C++) can handle although in practice that part is safe. See https://stackoverflow.com/questions/5929050/how-does-asn-1-encode-an-object-identifier and maybe https://stackoverflow.com/questions/11269401/how-do-i-use-ber-encoding-with-object-system-directoryservices-protocols-berconv . Or if you're actually dealing with SNMP via a library see if that doesn't already have some usable function(s). – dave_thompson_085 Jul 31 '17 at 01:20
  • 1
    print `sizeof string` inside the function and you'll see the problem – phuclv Jul 31 '17 at 01:24

1 Answers1

1

Note that in the code posted in the question, string[] is declared in the test() function using sizeof(hex_arr). But hex_arr is a pointer to char, not an array, so this will not give the correct size for string[]. Also, here string is an automatic variable whose lifetime is limited to the function test(); that is fine if the only goal is to print the results, but another solution must be found if the results need to be used elsewhere in the code.

One way to approach this is to pass an output array along with an input array into a conversion function. The function can return -1 if the input format is not acceptable, otherwise returning 0, and the output array can hold the identifier string resulting from the hex number input string.

Each pair of digits in the input represents a number in hexadecimal, which could be a three-digit decimal number, and each three-digit decimal number (except the last one, which is followed by a null-terminator) has an added delimiter added after it; so each pair of input digits converts to at most four characters. That means that allocating output[] to be twice the size of input will be sufficient to store any result, including space for the null-terminator. This in fact provides a little extra space, which will be needed.

The method used here by the id_from_hexstring() function to perform the conversion involves using sscanf() in a loop to read a pair of characters as a string from the input into the temporary buffer val_str[]. The %n conversion specifier is used to keep track of the location reached after each successful conversion, storing the number of successful matches in offset and using this value to increment a pointer into input[] in preparation for the next conversion.

The strtoul() function is used to convert the two-digit hex strings stored in val_str[] to unsigned long values. When the first pair of characters in input[] is converted, if the converted value does not match IN_PREFIX, the function returns with an error code. Otherwise, OUT_PREFIX is printed to the beginning of output[] instead of the converted value. After this first pair, the remaining conversion values are printed to the end of the output[] string.

After the conversion loop terminates, a trailing . delimiter is left at the end of output[], and the last task is to remove this before returning 0 to indicate success. By doubling the size (instead of the string length) of the input[] array to determine the size of the output[] array, it is guaranteed that output[] can hold the extra . delimiter.

This code could do more to validate the input. As it is now, the id_from_hexstring() function expects the input to be composed of valid hex characters. Here is the complete program and its output:

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

#define IN_PREFIX      0x2b
#define OUT_PREFIX     "1.3."
#define ID_PREFIX_ERR  -1
#define ID_PREFIX_OK   0

int id_from_hexstring(char *out, char *in);

int main(void)
{
    char input[] = "2b06010201010300";
    char output[sizeof input * 2] = "";

    if (id_from_hexstring(output, input) == -1) {
        fprintf(stderr, "Error in input format\n");
    } else {
        printf("Input: %s\n", input);
        printf("Output: %s\n", output);
    }

    return 0;
}

/* Expects that out points to a suitably sized array containing an empty
 * string, and in points to a string containing only unsigned hex digits */
int id_from_hexstring(char *out, char *in)
{
    int ret_val = ID_PREFIX_OK;
    int offset;
    char *ptr = in;
    char val_str[3];

    while (sscanf(ptr, "%2s%n", val_str, &offset) == 1) {

        unsigned long dec_val = strtoul(val_str, NULL, 16);

        /* Handle prefix; return error code if invalid */
        if (ptr == in) {
            if (dec_val != IN_PREFIX) {
            ret_val = ID_PREFIX_ERR;
            break;
            } else {
                sprintf(out, OUT_PREFIX);
                ptr += offset;
                continue;
            }
        }

        /* Print next value to identifier string */
        size_t end = strlen(out);
        sprintf(out + end, "%lu.", dec_val);
        ptr += offset;
    }

    /* Replace trailing point */
    size_t out_len = strlen(out);
    if (out_len > 0) {
        out[out_len - 1] = '\0';
    }

    return ret_val;
}
Input: 2b06010201010300
Output: 1.3.6.1.2.1.1.3.0
ad absurdum
  • 19,498
  • 5
  • 37
  • 60
  • Thank you for your answer. If input is a pointer to an array it doesn't work. `printf("%02x\n", ptr[0]);` works for the pointer but I can not convert the pointer with `id_from_hexstring` – Sam Aug 11 '17 at 14:37
  • @Sam-- The answer was written to the spec provided in the question, where the input is an array. If by "pointer to an array" you mean something like `char *input = "2b06010201010300";`, you can use `strlen()` instead of the `sizeof` operator, something like: `char output[(strlen(input) + 1) * 2];`. This requires that your compiler supports VLAs (required in C99, but optional in C11). The original code in the answer did not use a VLA, and would even compile under C89 if a couple of declarations were moved. – ad absurdum Aug 11 '17 at 19:04
  • @David-- Thanks, I used `char input[ * 2]; sprintf(&input[i*2],"%02x", ptr[i]);` to represent hex decimal as string. – Sam Aug 14 '17 at 16:38