Skip to content

Instantly share code, notes, and snippets.

@lafka
Created June 11, 2019 11:29
Show Gist options
  • Save lafka/e16e47ae63d6ef8ee610666661ef267d to your computer and use it in GitHub Desktop.
Save lafka/e16e47ae63d6ef8ee610666661ef267d to your computer and use it in GitHub Desktop.
/**
* Happy case-insensitive hex decoder which supports optional
* byte separators.
*
* `out` is a pointer to zero filled memory
*
* `size` is the size of `out`
*
* `in` MUST be null-terminated string.
*
* `ptr` an optional double pointer to `buf` which will be set to the start
* address of the decoded data.
*
* Returns -1 if non-hex (and non-separator characters) where found
* in input. Otherwise returns length. Valid separators are ':', '-' and ' '.
*
* Processing is done from left to right and `*ptr` is set to the start
* of the decoded data. Optionally the returned size can be used to
* deduce the starting point.
*
* Example (with *ptr):
*
* #define SIZE 64
* // returns {0xaa, 0xbb, 0xcc, 0xdd, 0xee}
* const char hexstr[] = "aabbccddee";
* uint8_t *ptr;
* buf = malloc(SIZE);
* memset(buf, 0, SIZE);
* length = hex_decode(buf, SIZE, hexstr, &ptr);
*
* for (int i = 0; i < length; i++) {
* printf("%02x ", ptr[i]);
* }
*
* printf("\n");
*
* free(buf);
*
*
* Example (with size):
* #define SIZE 64
* // returns {0xaa, 0x0b, 0xcc, 0xdd, 0xee}
* const char hexstr[] = "aa:b:cc dd ee";
* uint8_t *buf = malloc(SIZE);
* memset(buf, 0, SIZE);
* int length = hex_decode(buf, SIZE, hexstr, NULL);
*
* for (int i = 0; i < length; i++) {
* printf("%02x ", buf[SIZE - length + i]);
* }
*
* printf("\n");
*
* free(buf);
*
*/
int hex_decode(uint8_t *out, size_t size, const char *in, uint8_t **ptr) {
int pos = size - 1;
int insize = strlen(in);
int first = 0 == insize % 2;
/**
* Loop in reverse to easily fix padding.
*
* The process is like this:
* - First lowercase input by OR'ing with ' ' (a single space) - bit 5
* - check if it's a character or number by AND'ing with '@' - bit 6
* - If `first` is initially 1 we're processing the right most nibble
* - If we encounter a separator character we flip `first` thus making
* the next nibble the first (remember we're using modulo to "guess"
* which nibble we're working on).
*/
for (int i = strlen(in) - 1; i >= 0; i--) {
if (' ' == in[i] || '-' == in[i] || ':' == in[i]) {
/**
* if we've only processed the right most nibble we don't need to
* flip `first` since the next nibble will have same evenness
*/
if (first != i % 2) {
pos--;
continue;
}
first = !first;
continue;
}
//if ( ! (('0' > in[pos] && in[pos] < '0') || ('a >= in[pos] && in[pos] <= 'f'))) {
// return -1;
//}
out[pos] |= ((in[i] | ' ') - ('@' & in[i] ? 0x57 : 0x30)) << (first == i % 2 ? 0 : 4);
if (first != i % 2) {
pos--;
}
}
// If the last processed character was first nibble processed `pos` has not
// been decremented yet
if (NULL != ptr) {
*ptr = &out[pos + (first ? 1 : 0)];
}
return size - pos - (first ? 1 : 0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment