getBytes:length: (Question on the length parameter)

So, I had asked about this method previously, but I have some other questions that I am trying to figure out. I am working on a Core Bluetooth Application in which I programmed a peripheral device to transmit a heart rate value of 90 beats per minute. On iOS, I have discovered my service and my heart rate characteristic and am now working on processing that data, from the console output, the raw NSData characteristic.value data is 5a000000 which translates to a unsigned 32 bit integer value of 90 beats per minute. What I want to do now is to take that NSData data and turn store it as an unsigned 32 bit value and turn it into an NSNumber type and NSString for displaying on the screen. Thanks to some smart folks in the previous thread, I got my questions answered, but a few remain. Firstly, here is the code, specifically focusing on STEP 2:



// STEP 1: Make rawData and initialize 32-bit integer value:
NSData *rawData = characteristic.value;
uint32_t rawInt = 0;

// STEP 2: Copy data from rawData into address of rawInt
[rawData getBytes:&rawInt length:sizeof(rawInt)];

// STEP 3: Turn to NSNumber and then String
self.heartRateValue = [[NSNumber alloc]initWithUnsignedInt:rawInt];
[self.heartRateLabel setText:[self.heartRateValue stringValue]];



Questions:


1 - So as I understand it is that we are send a message to NSData *rawData variable to give the data it has inside so we can copy that data into our rawInt variable's address?


2 - For the length parameter, I chose sizeof rawInt, but should I be doing sizeof rawData instead? Would I lose data as a result? Why would i choose sizeOf rawInt vs. rawData and vice versa?

1. Yes. It's also possible to access the uint32_t value inside the NSData's byte storage without copying the bytes out to a separate variable. However, since that has some potential pitfalls if you're not careful, I won't follow that sidetrack unless you specifically want to.


2. No, sizeof (rawInt) is correct, for two reasons:


— That's the number of bytes for a uint32_t value. rawData is an object reference, aka a pointer, and so sizeof (rawData) is the size of a pointer, or 8 bytes, which is the wrong number. The data that rawData contains is stored separately (and its length can be obtained as "rawData.length").


— Whenever you copy memory, for safety's sake you want to copy at most the size of the destination, not the size of the source. Otherwise you've created a buffer overflow bug, which can crash your app.


"But," I hear you ask, "isn't rawData.length equal to 4?" Well, presumably it should be, but since the data comes from your I/O device, it might on occasion be less than 4 or greater than 4. For that reason, your step 2 really ought to check that "rawData.length == sizeof (rawInt)", or possibly "rawData.length >= sizeof (rawInt)" if your device actually returns something else on the end of the buffer.

Response to Point # 1:

Thanks for clearing that up! I will stick with what I am doing for now, but I would like to learn about the technique you mentioned about accessing the values without copying.


Response to Point # 2:

Awesome advice, and thanks for the tips and best practices around this area.

You already know most of it, because we've discussed it before. The property "rawData.bytes" returns a "const void*" pointer to the actual data, consisting of 4 bytes (we hope). Instead of copying that into a separate variable, you can reference it in-place like this:


     uint32_t rawInt = *(const uint32_t*) rawData.bytes;


That is, you tell the compiler to re-interpret the pointer to the raw data bytes as a pointer to a uint32_t-sized sequence of bytes. The effect is identical to your original code, except that you rely on the compiler to know how many bytes to get, which means you can't do it wrong (assuming there are at least 4 bytes to get).


The only potential problem is that there's a memory management question about how long the pointer remains valid. In very simple code like the above, it's safe to assume that it's valid for long enough to set the value of rawInt. But it gets subtle very quickly. For example, this version of the code:


     const uint32_t* rawIntPtr = rawData.bytes;
     uint32_t rawInt = *rawIntPtr;


looks basically the same, except that it breaks the first version up into 2 sub-steps. (You might do this, for example, if you think you need to use rawIntPtr multiple times in subsequent code.) Unfortunately, this second version is not safe, since the lifetime guarantee for the rawData.bytes pointer ends after the first line. After that, rawIntPtr is not necessarily a valid pointer to anything.


Reasoning about this is difficult in general, and going from something valid to something invalid is all too easy. Out of the two valid choices, I prefer the one from the current post:


     uint32_t rawInt = *(const uint32_t*) rawData.bytes;


since it's a little clearer to the reader ("I am re-interpreting some bytes as a 32-bit integer") than the "getBytes" version ("I am randomly moving some bytes around in memory, and I'll leave it to you to figure out why"). And just a tad safer, in the grand scheme of things.

getBytes:length: (Question on the length parameter)
 
 
Q