10

fairly new iPhone developer here. Building an app to send RS232 commands to a device expecting them over a TCP/IP socket connection. I've got the comms part down, and can send ASCII commands fine. It's the hex code commands I'm having trouble with.

So lets say I have the following hex data to send (in this format):

\x1C\x02d\x00\x00\x00\xFF\x7F

How do I convert this into an NSData object, which my send method expects?

Obviously this does not work for this hex data (but does for standard ascii commands):

NSString *commandascii;
NSData *commandToSend;
commandascii = @"\x1C\x02d\x00\x00\x00\xFF\x7F";
commandToSend = [commandascii dataUsingEncoding:NSStringEncoding];

For a start, some of the \x hex codes are escape characters, and I get an "input conversion stopped..." warning when compiling in XCode. And NSStringEncoding obviously isn't right for this hex string either.

So the first problem is how to store this hex string I guess, then how to convert to NSData.

Any ideas?

Max Clarke
  • 1,148
  • 2
  • 11
  • 19
  • Thanks for all the responses, you've all been a big help and I've learnt alot! I've constructed the above code which is a bit of an unintentional amalgam of a few of the answers. This works for strings like "00 3c 5f 22 ef 00 00 ff" which it turns out I can get rather than the \x style. Thanks again for all the help! – Max Clarke Mar 01 '10 at 01:37

9 Answers9

31

Code for hex in NSStrings like "00 05 22 1C EA 01 00 FF". 'command' is the hex NSString.

command = [command stringByReplacingOccurrencesOfString:@" " withString:@""];
NSMutableData *commandToSend= [[NSMutableData alloc] init];
unsigned char whole_byte;
char byte_chars[3] = {'\0','\0','\0'};
for (int i = 0; i < ([command length] / 2); i++) {
    byte_chars[0] = [command characterAtIndex:i*2];
    byte_chars[1] = [command characterAtIndex:i*2+1];
    whole_byte = strtol(byte_chars, NULL, 16);
    [commandToSend appendBytes:&whole_byte length:1]; 
}
NSLog(@"%@", commandToSend);
Jeff Lockhart
  • 5,379
  • 4
  • 36
  • 51
Max Clarke
  • 1,148
  • 2
  • 11
  • 19
  • would be nice if you commented some and made it more generic so you don't need to use a 8 hex-bytes in the string. otherwise nice thx – Robin Rye Aug 18 '11 at 14:23
  • 1
    it worked for me. If you have a generic hex string (and not an 8-hex-bytes as in the example), use [command length]/2 instead of 8 in the for loop. – Fmessina Jan 22 '13 at 14:12
  • Thats awesome Max! Thank you! – Max Mar 16 '15 at 14:21
9

Here's an example decoder implemented on a category on NSString.

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

unsigned char strToChar (char a, char b)
{
    char encoder[3] = {'\0','\0','\0'};
    encoder[0] = a;
    encoder[1] = b;
    return (char) strtol(encoder,NULL,16);
}

@interface NSString (NSStringExtensions)
- (NSData *) decodeFromHexidecimal;
@end

@implementation NSString (NSStringExtensions)

- (NSData *) decodeFromHexidecimal;
{
    const char * bytes = [self cStringUsingEncoding: NSUTF8StringEncoding];
    NSUInteger length = strlen(bytes);
    unsigned char * r = (unsigned char *) malloc(length / 2 + 1);
    unsigned char * index = r;

    while ((*bytes) && (*(bytes +1))) {
        *index = strToChar(*bytes, *(bytes +1));
        index++;
        bytes+=2;
    }
    *index = '\0';

    NSData * result = [NSData dataWithBytes: r length: length / 2];
    free(r);

    return result;
}

@end
xyzzycoder
  • 1,831
  • 13
  • 19
  • Thanks xyzzy, though this doesn't compile - it has issues with safeMalloc, strToChar and safeStrLen. Do I need to include/import something for these functions to work? – Max Clarke Feb 26 '10 at 03:29
  • Ah, right. You can replace these calls w/ malloc and strlen. I'll update the response. – xyzzycoder Feb 26 '10 at 17:43
4

If you can hard code the hex data:

const char bytes[] = "\x00\x12\x45\xAB";
size_t length = (sizeof bytes) - 1; //string literals have implicit trailing '\0'

NSData *data = [NSData dataWithBytes:bytes length:length];

If your code must interpret the hex string (assuming the hex string is in a variable called inputData and lengthOfInputData is the length of inputData):


#define HexCharToNybble(x) ((char)((x > '9') ? tolower(x) - 'a' + 10 : x - '0') & 0xF)

int i;

NSMutableData *data = [NSMutableData data];

for (i = 0; i < lengthOfInputData;)
{
    char byteToAppend;

    if (i < (lengthOfInputData - 3) &&
        inputData[i+0] == '\\' &&
        inputData[i+1] == 'x' &&
        isxdigit(inputData[i+2]) &&
        isxdigit(inputData[i+3]))
    {
        byteToAppend = HexCharToNybble(inputData[i+2]) << 4 + HexCharToNybble(input[i+3]);
        i += 4;
    }
    else
    {
        byteToAppend = inputData[i];
        i += 1;
    }

    [data appendBytes:&byteToAppend length:1];
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
  • Woah, that's ugly. Sorry if I have blinded anyone with that source code. It kinda looked ugly as I was writing it but decided to finish it anyway. – dreamlax Feb 26 '10 at 04:05
2

This is an old topic, but I'd like to add some remarks.

• Scanning a string with [NSString characterAtIndex] is not very efficient. Get the C string in UTF8, then scan it using a *char++ is much faster.

• It's better to allocate NSMutableData with capacity, to avoid time consuming block resizing. I think NSData is even better ( see next point )

• Instead of create NSData using malloc, then [NSData dataWithBytes] and finally free, use malloc, and [NSData dataWithBytesNoCopy:length:freeWhenDone:]

It also avoids memory operation ( reallocate, copy, free ). The freeWhenDone boolean tells the NSData to take ownership of the memory block, and free it when it will be released.

• Here is the function I have to convert hex strings to bytes blocks. There is not much error checking on input string, but the allocation is tested.

The formatting of the input string ( like remove 0x, spaces and punctuation marks ) is better out of the conversion function. Why would we lose some time doing extra processing if we are sure the input is OK.

+(NSData*)bytesStringToData:(NSString*)bytesString
{
    if (!bytesString || !bytesString.length) return NULL;
    // Get the c string
    const char *scanner=[bytesString cStringUsingEncoding:NSUTF8StringEncoding];
    char twoChars[3]={0,0,0};
    long bytesBlockSize = formattedBytesString.length/2;
    long counter = bytesBlockSize;
    Byte *bytesBlock = malloc(bytesBlockSize);
    if (!bytesBlock) return NULL;
    Byte *writer = bytesBlock;
    while (counter--) {
        twoChars[0]=*scanner++;
        twoChars[1]=*scanner++;
        *writer++ = strtol(twoChars, NULL, 16);
    }
    return[NSData dataWithBytesNoCopy:bytesBlock length:bytesBlockSize freeWhenDone:YES];
}
Kampai
  • 22,848
  • 21
  • 95
  • 95
Moose
  • 2,607
  • 24
  • 23
1

If I want to hard-code the bytes, I do something like this:

enum { numCommandBytes = 8 };
static const unsigned char commandBytes[numCommandBytes] = { 0x1c, 0x02, 'd', 0x0, 0x0, 0x0, 0xff, 0x7f };

If you're obtaining these backslash-escaped bytes at run time, try the strunvis function.

Obviously this does not work for this hex data (but does for standard ascii commands):

NSString *commandascii;
NSData *commandToSend;
commandascii = @"\x1C\x02d\x00\x00\x00\xFF\x7F";
commandToSend = [commandascii dataUsingEncoding:NSStringEncoding];

For a start, some of the \x hex codes are escape characters, and I get an "input conversion stopped..." warning when compiling in XCode. And NSStringEncoding obviously isn't right for this hex string either.

First, it's Xcode, with a lowercase c.

Second, NSStringEncoding is a type, not an encoding identifier. That code shouldn't compile at all.

More to the point, backslash-escaping is not an encoding; in fact, it's largely independent of encoding. The backslash and 'x' are characters, not bytes, which means that they must be encoded to (and decoded from) bytes, which is the job of an encoding.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
0

Another way to do it.

-(NSData *) dataFromHexString:(NSString *) hexstr
{
    NSMutableData *data = [[NSMutableData alloc] init];
    NSString *inputStr = [hexstr uppercaseString];

    NSString *hexChars = @"0123456789ABCDEF";

    Byte b1,b2;
    b1 = 255;
    b2 = 255;
    for (int i=0; i<hexstr.length; i++) {
        NSString *subStr = [inputStr substringWithRange:NSMakeRange(i, 1)];
        NSRange loc = [hexChars rangeOfString:subStr];

        if (loc.location == NSNotFound) continue;

        if (255 == b1) {
            b1 = (Byte)loc.location;
        }else {
            b2 = (Byte)loc.location;

            //Appending the Byte to NSData
            Byte *bytes = malloc(sizeof(Byte) *1);
            bytes[0] = ((b1<<4) & 0xf0) | (b2 & 0x0f);
            [data appendBytes:bytes length:1];

            b1 = b2 = 255;
        }
    }

    return data;
}
dreamlax
  • 93,976
  • 29
  • 161
  • 209
Amit
  • 1,128
  • 6
  • 15
  • 29
  • This is a pretty inefficient algorithm, using both `subStringWithRange:` and `rangeOfString:` ... it seems unnecessary. Also, I think it would be better to try and read two bytes at a time rather than use a mini state machine. – dreamlax Aug 04 '14 at 07:04
0
-(NSData*) convertToByteArray:(NSString*) command {
    if (command == nil || command.length == 0) return nil;
    NSString *command1 = command;
    if(command1.length%2 != 0) {
        // to handle odd bytes like 1000 decimal = 3E8 with is of length = 3
        command1 = [NSString stringWithFormat:@"0%@",command1];
    }
    NSUInteger length = command1.length/2 ;
    NSMutableData *commandToSend = [[NSMutableData alloc] initWithLength:length];
    char byte_chars[3] = {'\0','\0','\0'};
    unsigned char whole_byte;
    for (int i=0; i<length; i++) {
        byte_chars[0] = [command1 characterAtIndex:i*2];
        byte_chars[1] = [command1 characterAtIndex:i*2+1];
        whole_byte = strtol(byte_chars, NULL, 16);
        [commandToSend appendBytes:&whole_byte length:1];
    }
    NSRange commandRange = NSMakeRange(commandToSend.length - length, length);
    NSData *result = [commandToSend subdataWithRange:commandRange];
    return result;
}
-1

I know this is a very old thread, but there is an encoding scheme in Objective C that can easily convert your string of hex codes into ASCII characters.

1) remove the \x from the string and with out keeping spaces in the string just convert the string to NSData using :

[[NSData alloc] initWithData:[stringToBeConverted dataUsingEncoding:NSASCIIStringEncoding]];
Kampai
  • 22,848
  • 21
  • 95
  • 95
  • 1
    No. The OP doesn't want to convert the string itself into data. He want's to convert the **hexadecimal representation** of the data back to the data itself. – Julian F. Weinert Dec 12 '14 at 13:52
-3

Hex data is just bytes in memory, you think of it as a string because that's how you see it but they could represent anything. Try: (typed in the browser, may contain errors)

NSMutableData *hexData = [[NSMutableData alloc] init];

[hexData appendBytes: 0x1C];
[hexData appendBytes: 0x02D];

etc...

  • This looks more like what I'm after, thanks Bruce. Doesn't quite work though, after fixing up the syntax (should be appendbytes: length:) I get a "warning: passing argument 1 of 'appendBytes:length:' makes pointer from integer without a cast" Any ideas? – Max Clarke Feb 26 '10 at 03:27
  • 3
    `appendBytes:length:` expects a *pointer* to the bytes as well as the number of bytes you're appending. – dreamlax Feb 26 '10 at 03:45