objective-ciostitaniumnsdatatitanium-modules

How to convert NSArray of NSNumbers into NSData


I'm new to Objective C and am modifying an iOS module for the Titanium framework which sends UDP packets. The module currently lets you pass in a text string to send, and it will convert it to bytes and send it via UDP to a destination ip and port. This works great and here is the code:

https://github.com/chrisfjones/titanium_module_udp/blob/master/UDPSocketProxy.m

What I want to do is pass a byte array into the send function instead of a string and have it just send it. Here is the Titanium code:

var udp = require('chrisfjones.titanium_module_udp');
var socket = udp.createUDP();
var bytes = [ 100, 15, 132, 53, 14, 246, 0, 0, 0, 0, 196, 209, 1, 1, 0, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 255, 16, 0, 45, 120, 0, 0, 0, 0, 158, 4, 111, 30, 179, 41 ];
socket.send(bytes, "1.2.3.4", 6100);

And here is the new send function so far:

- (void) sendBytes: (NSArray*) args {

    NSArray *msg       = (NSArray*)[args objectAtIndex: 0];

    NSString *host      = [TiUtils stringValue:[args objectAtIndex: 1]];

    NSInteger port      = [TiUtils intValue:   [args objectAtIndex: 2]];


NSLog(@"%@ send bytes: %@ to %@:%i", self, msg, host, port);


struct sockaddr_in destinationAddress;

    socklen_t sockaddr_destaddr_len = sizeof(destinationAddress);



    memset(&destinationAddress, 0, sockaddr_destaddr_len);

    destinationAddress.sin_len = (__uint8_t) sockaddr_destaddr_len;

    destinationAddress.sin_family = AF_INET;

    destinationAddress.sin_port = htons(port);

    destinationAddress.sin_addr.s_addr = inet_addr([host cStringUsingEncoding: NSUTF8StringEncoding]);



    NSData *destinationAddressData = [NSData dataWithBytes:&destinationAddress length:sizeof(destinationAddress)];



    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:msg];



    CFSocketError socket_error = CFSocketSendData(_socket, (CFDataRef) destinationAddressData, (CFDataRef) data, 10);

    if (socket_error) {

        NSLog(@"socket error: %li", socket_error);

    } else {

        NSLog(@"sent bytes: '%@' to %@:%i", msg, host, port);

    }

}

You'll notice that it passes in a NSArray. That is because Titanium converts the javascript array that I create into a NSArray of NSNumber objects. I read that this is terribly inefficient but it's built into the Titanium framework so I don't see a way around it, so I'm hoping for an answer on how to make it work with this getting passed in, not a lecture on how inefficient it is.

When I call the new send method, instead of it sending the 50 or so bytes that I pass in, I can see in wireshark that it is actually passing over 1000 bytes. I'm assuming the issue is with the conversion on this line:

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:msg];

Can someone please help on how to just send the byte array that I pass in? Thanks!


Solution

  • Yes, you don't want to use the archiver in this case, since you're just trying to turn a set of bytes into a block of NSData. Depending on whether you are passing in an array of NSNumber or an array of NSString, you'll basically need to loop over the contents of the array and append the data to an NSMutableData.

    Assuming it's an array of NSNumber, then something like this should work:

    NSMutableData *data = [[NSMutableData alloc] initWithCapacity: [msg count]];
    for( NSNumber *number in msg) {
        char byte = [number charValue];
        [data appendBytes: &byte length: 1];
    }
    // .... code that uses data ...
    [data release];
    

    If the numbers are numeric values in string form, you'll probably want to use the -(int)intValue method of NSString to pull out the data and then add that to the data, basically changing the above to:

    NSMutableData *data = [[NSMutableData alloc] initWithCapacity: [msg count]];
    for( NSString *string in msg) {
        char byte = (char)[string intValue];
        [data appendBytes: &byte length: 1];
    }
    // .... code that uses data ...
    [data release];
    

    And, if you're trying to stuff the characters from the strings, then you'll need to grab the character using [string characterAtIndex: 0] and compensate for the fact that you will be receiving a unichar instead of a char.