How to use enumerateByteRangesUsingBlock in Objective-C? - ios

The method enumerateByteRangesUsingBlock: is in class NSData, and interpreted in Apple Documentation as following:
Enumerate through each range of bytes in the data object using a block.
- (void)enumerateByteRangesUsingBlock:(void (^)(const void *bytes, NSRange byteRange, BOOL *stop))block
Parameters
block
The block to apply to byte ranges in the array.
The block takes three arguments:
bytes
The bytes for the current range.
byteRange
The range of the current data bytes.
stop
A reference to a Boolean value. The block can set the value to YES to stop further processing of the data. The stop argument is an out-only argument. You should only ever set this Boolean to YES within the Block.
Discussion
The enumeration block is called once for each contiguous region of memory in the receiver (once total for a contiguous NSData object), until either all bytes have been enumerated, or the stop parameter is set to YES.
But my question is, when will the block be executed? Which method is responsible for providing arguments bytes, byteRange and stop for the block? For example, if I want to traverse a part of the bytes array, what should I do to control?

The bytes, byteRange and stop parameters are passed to your block by enumerateByteRangesUsingBlock. You don't specify which bytes you want to traverse - you use this method to traverse all of the bytes (You can terminate early via stop).
As a simple example, say you wanted to search through some NSData looking for a 0xff. You could use -
NSInteger ffFound=NSNotFound;
[myData enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
for (NSInteger i=0;i<byteRange.length;i++) {
if (bytes[i]== 0xff) {
ffFound=byteRange.location+i;
*stop=YES;
break;
}
}
}];
if (ffFound != NSNotFound) {
NSLog(#"0xFF was found at location %ld",(long)ffFound);
}

The other answers are good, but didn't answer this part:
when will the block be executed?
The block will be executed immediately, one or more times, before -enumerateByteRangesUsingBlock: returns.
You may be confused because many methods which take a block are asynchronous. They return but retain the block and continue to do some work (or monitor some events or input) in the background and call the block later.
-enumerateByteRangesUsingBlock: is not like that. It's synchronous. It does all of its work before returning to the caller.

[NSData enumerateByteRangesUsingBlock:] is used for reading contiguous region of memory of fetched data which means that as the iterator encounters a new chunk, it will execute the block passed to it.
In addition to that, it is not guaranteed that the block will be called more than once (in other words, data may not be fragmented) and the size of bytes passed to block will always be same.
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:#"http://httpbin.org/image/png"]];
[data enumerateByteRangesUsingBlock:^(const void *bytes, NSRange byteRange, BOOL *stop) {
NSLog(#"You get the chunk in range: %#", NSStringFromRange(byteRange));
}];
Alternatively, you can also enumerate over the data and read fixed size of chunk at each iteration;
NSUInteger length = [data length];
NSUInteger chunkSize = 1024;
NSUInteger chunkOffset = 0;
do {
NSUInteger chunkSize = MIN(length - chunkOffset, chunkSize);
NSData *chunk = [data subdataWithRange:NSMakeRange(chunkOffset, chunkSize)];
chunkOffset = chunkOffset + chunkSize;
} while (chunkOffset < length);

Related

NSData to NSArray of NSNumbers?

I'm coming from Swift to Objective-C and a problem I have run into is that NSData doesn't seem have methods to enumerate over UInt8 values the way Swift's Data does. Other answers with similar titles exist but they all deal with property lists, whereas I just have a bucket of ASCII bytes. Specifically, the code I want to replicate in Obj-C is:
//Find the next newline in ASCII data given a byte offset
let subset = myData.advanced(by: offset)
var lineEnd: Data.Index = myData.endIndex
subset.enumerateBytes({ (memory, idx, stop) in
let newline: UInt8 = 10
for idx in memory.indices {
let charByte = memory[idx]
if charByte == newline {
lineEnd = idx
stop = true; return;
}
}
})
Ideally I want a way to convert an NSArray to an array of NSNumbers which I can extract the intValues from. Of course, if there is a better method, let me know. A goal is to keep the Obj-C code as similar to Swift as possible as I will be maintaining the two codebases simultaneously.
The only good way to access an NSData's bytes is to call its -bytes method, which gets you a C pointer to its internal storage.
NSData *data = ...;
const uint8_t *bytes = data.bytes;
NSUInteger length = data.length;
for (NSUInteger i = 0; i < length; i++) {
uint8_t byte = bytes[i];
// do something with byte
}
The closest equivalent to advance would be subdataWithRange. The equivalent to enumerateBytes is enumerateByteRangesUsingBlock. It would yield something like:
NSData *subset = [data subdataWithRange:NSMakeRange(offset, data.length - offset)];
__block NSUInteger lineEnd = data.length;
Byte newline = 10;
[subset enumerateByteRangesUsingBlock:^(const void * _Nonnull bytes, NSRange byteRange, BOOL * _Nonnull stop) {
for (NSInteger index = 0; index < byteRange.length; index++) {
Byte charByte = ((Byte *)bytes)[index];
if (charByte == newline) {
lineEnd = index + byteRange.location;
*stop = true;
return;
}
}
}];
Note, I made a few changes from your Swift example:
If the data was not contiguous, your Swift example returns the index within the current block. But I suspect you want the location within subset, not the current block. I'm wagering that you've never noticed this because it's pretty rare that NSData blocks are not continuous.
But the Swift code doesn't look correct to me. This Objective-C example reports the offset within subset, not within the current block within subset.
It's not observable performance difference, but I pulled the definition of newline out of the enumeration block. Why repeatedly define that?
If you're really searching for a character in the NSData, I'd suggest avoiding creating the subset altogether. Just use rangeOfData:options:range:. This will find whatever you're looking for.

How to Convert NSValue to NSString

Some background... I am writing code that interacts with javascript via a ObjC-JS bridge utilizing UIWebView's stringByEvaluatingJavaScriptFromString:. The idea is that the "brains" of the app be in JS which tells Objective-C how to behave. There are multiple benefits to this like reduced binary size, flexible updates, etc. However, there is a case where there is some Objective-C only object that the JS needs to have a reference to (JS instructs ObjC when to use/remove the object). This is being done by placing the native object in a dictionary with a unique identifier which can be passed as a string to JS (over the bridge). My problem stems with coming up with a nice identifier for said native Objective-C object.
Thus, I am trying to convert a reference to an object to a string with no luck. This is what I have:
// anObject is a custom class
NSValue *handle = [NSValue valueWithPointer:(__bridge const void *)anObject];
NSData *data = [NSData dataWithValue:handle];
NSString *stringHandle = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
The dataWithValue: function (taken from this SO post):
+ (NSData *)dataWithValue:(NSValue *)value {
NSUInteger size;
const char* encoding = [value objCType];
NSGetSizeAndAlignment(encoding, &size, NULL);
void* ptr = malloc(size);
[value getValue:ptr];
NSData* data = [NSData dataWithBytes:ptr length:size];
free(ptr);
return data;
}
Walking through it in the debugger shows me a nil value for stringHandle:
What am I doing wrong?
What you're doing wrong is trying to treat an address as if it's a UTF-8 encoded string. An address -- or any other chunk of arbitrary data -- isn't very likely to be valid UTF-8 data. (If by chance it were, it still wouldn't be the string you expect.)
If you're trying to get a string containing the pointer value, i.e., the address of the original object, that's just [NSString stringWithFormat:#"%p", anObject];
If you really need to do it from the NSValue, then replace anObject with [theValue pointerValue].
If you want to pretty-print arbitrary data, see How to convert an NSData into an NSString Hex string?
You can get a string representation by calling the NSObject method "description". You can override the "description" method in a subclass if you need.
An NSValue of a pointer will be an object holding the 4 bytes of the 32-bit pointer. It will not hold any of the data pointed to in RAM.

Why does the following code return incorrect values from NSData?

I need to send data across the network as NSData. As the format may be determined only at runtime, (e.g.: Message Type, number of objects etc, types of objects), I am using the following code to pack / unpack the NSData
To pack:
NSMutableData *data = [NSMutableData dataWithCapacity:0];
unsigned int _state = 66;
[data appendBytes:&state length:sizeof(state)];
To unpack (after receiving on a different iOS device)
void *buffer = malloc(255);
[data getBytes:buffer length:sizeof(unsigned int)];
unsigned int _state = (unsigned int)buffer;
....
I am using the buffer, because eventually there will be many different ints/ floats etc stored in the NSData. The first int may determine the type of message, second int - the number of floats stored, etc... I send and receive the data using apples game center apis:
- (BOOL)sendData:(NSData *)data toPlayers:(NSArray *)playerIDs withDataMode:(GKMatchSendDataMode)mode error:(NSError **)error
-(void)match:(GKMatch *)match didReceiveData:(NSData *)data fromPlayer:(NSString *)playerID
But the problem is, when I unpack the single int, instead of getting 66, I get some random value like 401488960 or 399903824 (its different each time I unpack, even though I am sending 66 each time). Why is the data incorrect? Am I unpacking incorrectly?
You are casting the pointer buffer to unsigned int: you are assigning the memory address to _state, not the value at that address. Use a pointer of the appropriate type (unsigned int *) instead, and dereference it:
unsigned int _state = *(unsigned int *)buffer;

Obfuscating a number(in a string) Objective C

I'm using the following code to obfuscate a passcode for a test app of mine.
- (NSString *)obfuscate:(NSString *)string withKey:(NSString *)key
{
// Create data object from the string
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
// Get pointer to data to obfuscate
char *dataPtr = (char *) [data bytes];
// Get pointer to key data
char *keyData = (char *) [[key dataUsingEncoding:NSUTF8StringEncoding] bytes];
// Points to each char in sequence in the key
char *keyPtr = keyData;
int keyIndex = 0;
// For each character in data, xor with current value in key
for (int x = 0; x < [data length]; x++)
{
// Replace current character in data with
// current character xor'd with current key value.
// Bump each pointer to the next character
*dataPtr = *dataPtr++ ^ *keyPtr++;
// If at end of key data, reset count and
// set key pointer back to start of key value
if (++keyIndex == [key length])
keyIndex = 0, keyPtr = keyData;
}
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
This works like a charm with all strings, but i've ran into a bit of a problem comparing the following results
NSLog([[self obfuscate:#"0000", #"maki"]); //Returns 0]<W
NSLog([[self obfuscate:#"0809", #"maki"]); //Returns 0]<W
As you can see, the two strings with numbers in, while different, return the same result! Whats gone wrong in the code i've attached to result in the same result for these two numbers?
Another example:
NSLog([self obfuscate:#"8000" withKey:#"maki"]); //Returns 8U4_
NSLog([self obfuscate:#"8290" withKey:#"maki"]); //Returns 8U4_ as well
I may be misunderstanding the concept of obfuscation, but I was under the impression that each unique string returns a unique obfuscated string!
Please help me fix this bug/glitch
Source of Code: http://iosdevelopertips.com/cocoa/obfuscation-encryption-of-string-nsstring.html
The problem is your last line. You create the new string with the original, unmodified data object.
You need to create a new NSData object from the modified dataPtr bytes.
NSData *newData = [NSData dataWithBytes:dataPtr length:data.length];
return [[NSString alloc] initWithData:newData encoding:NSUTF8StringEncoding];
But you have some bigger issues.
The calls to bytes returns a constant, read-only reference to the bytes in the NSData object. You should NOT be modifying that data.
The result of your XOR on the character data could, in theory, result in a byte stream that is no longer a valid UTF-8 encoded string.
The obfuscation algorithm that you have selected is based on XORing the data and the "key" values together. Generally, this is not very strong. Moreover, since XOR is symmetric, the results are very prone to producing duplicates.
Although your implementation is currently broken, fixing it would not be of much help in preventing the algorithm from producing identical results for different data: it is relatively straightforward to construct key/data pairs that produce the same obfuscated string - for example,
[self obfuscate:#"0123" withKey:#"vwxy"]
[self obfuscate:#"pqrs" withKey:#"6789"]
will produce identical results "FFJJ", even though both the strings and the keys look sufficiently different.
If you would like to "obfuscate" your strings in a cryptographically strong way, use a salted secure hash algorithm: it will produce very different results for even slightly different strings.

Find Character String In Binary Data

I have a binary file I've loaded using an NSData object. Is there a way to locate a sequence of characters, 'abcd' for example, within that binary data and return the offset without converting the entire file to a string? Seems like it should be a simple answer, but I'm not sure how to do it. Any ideas?
I'm doing this on iOS 3 so I don't have -rangeOfData:options:range: available.
I'm going to award this one to Sixteen Otto for suggesting strstr. I went and found the source code for the C function strstr and rewrote it to work on a fixed length Byte array--which incidentally is different from a char array as it is not null terminated. Here is the code I ended up with:
- (Byte*)offsetOfBytes:(Byte*)bytes inBuffer:(const Byte*)buffer ofLength:(int)len;
{
Byte *cp = bytes;
Byte *s1, *s2;
if ( !*buffer )
return bytes;
int i = 0;
for (i=0; i < len; ++i)
{
s1 = cp;
s2 = (Byte*)buffer;
while ( *s1 && *s2 && !(*s1-*s2) )
s1++, s2++;
if (!*s2)
return cp;
cp++;
}
return NULL;
}
This returns a pointer to the first occurrence of bytes, the thing I'm looking for, in buffer, the byte array that should contain bytes.
I call it like this:
// data is the NSData object
const Byte *bytes = [data bytes];
Byte* index = [self offsetOfBytes:tag inBuffer:bytes ofLength:[data length]];
Convert your substring to an NSData object, and search for those bytes in the larger NSData using rangeOfData:options:range:. Make sure that the string encodings match!
On iPhone, where that isn't available, you may have to do this yourself. The C function strstr() will give you a pointer to the first occurrence of a pattern within the buffer (as long as neither contain nulls!), but not the index. Here's a function that should do the job (but no promises, since I haven't tried actually running it...):
- (NSUInteger)indexOfData:(NSData*)needle inData:(NSData*)haystack
{
const void* needleBytes = [needle bytes];
const void* haystackBytes = [haystack bytes];
// walk the length of the buffer, looking for a byte that matches the start
// of the pattern; we can skip (|needle|-1) bytes at the end, since we can't
// have a match that's shorter than needle itself
for (NSUInteger i=0; i < [haystack length]-[needle length]+1; i++)
{
// walk needle's bytes while they still match the bytes of haystack
// starting at i; if we walk off the end of needle, we found a match
NSUInteger j=0;
while (j < [needle length] && needleBytes[j] == haystackBytes[i+j])
{
j++;
}
if (j == [needle length])
{
return i;
}
}
return NSNotFound;
}
This runs in something like O(nm), where n is the buffer length, and m is the size of the substring. It's written to work with NSData for two reasons: 1) that's what you seem to have in hand, and 2) those objects already encapsulate both the actual bytes, and the length of the buffer.
If you're using Snow Leopard, a convenient way is the new -rangeOfData:options:range: method in NSData that returns the range of the first occurrence of a piece of data. Otherwise, you can access the NSData's contents yourself using its -bytes method to perform your own search.
I had the same problem.
I solved it doing the other way round, compared to the suggestions.
first, I reformat the data (assume your NSData is stored in var rawFile) with:
NSString *ascii = [[NSString alloc] initWithData:rawFile encoding:NSAsciiStringEncoding];
Now, you can easily do string searches like 'abcd' or whatever you want using the NSScanner class and passing the ascii string to the scanner. Maybe this is not really efficient, but it works until the -rangeOfData method will be available for iPhone also.

Resources