NSMutableData's bytes to UnsafeMutableRawPointer not updating the value of mutableData

I am having a struct object. And a method, whose input is payload. Now I am creating a mutableData named packet, and it's mutable bytes are referring to ICMPHeader struct.



struct ICMPHeader {
    var type:UInt8
    var code:UInt8
    var checksum:UInt16
    var identifier:UInt16
    var sequenceNumber:UInt16
};


func createPacket(payload:NSData) -> NSData(){
    var packet:NSMutableData?
    var icmpPtr:ICMPHeader = ICMPHeader(type: 0, code: 0, checksum: 0, identifier: 0, sequenceNumber: 0)
    packet = NSMutableData(length: Int(MemoryLayout<ICMPHeader>.size + payload.length))

    if packet != nil {

       icmpPtr = packet!.mutableBytes.assumingMemoryBound(to: ICMPHeader.self).pointee

       icmpPtr.type = type
       icmpPtr.code = 0
       icmpPtr.checksum = 0
       icmpPtr.identifier = CFSwapInt16BigToHost(identifier)
       icmpPtr.sequenceNumber = CFSwapInt16HostToBig(identifier)
       memcpy(&icmpPtr + 1, payload.bytes, payload.length)

       if (requiresChecksum) {
           icmpPtr.checksum = in_cksum(packet!.bytes, bufferLen: packet!.length);
       }

   }
   return packet
}


Mutable bytes are successfully getting binded to struct, and values are also getting updated in struct ICMPHeader.


The issue is changing the values in struct is not changing the value of mutable data packet.


And if, I am trying to recreate the packet after creating struct, then it is crashing.

package = NSMutableData(bytes: unsafeBitCast(icmpPtr, to: UnsafeMutableRawPointer.self), length: Int(MemoryLayout<ICMPHeader>.size + payload.length))

Replies

The immediate cause of your problem is this:

icmpPtr = packet!.mutableBytes.assumingMemoryBound(to: ICMPHeader.self).pointee
icmpPtr
is not a pointer, its a
ICMPHeader
value, and, as with all value types, changes to it do not get reflected elsewhere.

However, there’s a bunch of other things I’d change in your code:

  • From what you posted it seems like you’re defining

    ICMPHeader
    in Swift. That’s not good. Swift makes no guarantees about how such a structure would be laid out. You’ll have to define it in C (as shown below) and then import that into Swift. Note my use of
    __Check_Compile_Time
    to check, at compile time, that the compiler has laid out the structure correctly.
  • Swift has had native endian swizzling for a while now.

  • In my experience Foundation value types are much easier to use than NSData.

Here’s how I’d write this code:

import Foundation

func in_cksum(buffer: UnsafeBufferPointer<UInt8>) -> UInt16 {
    return 12345
}

func createPacket(payload: Data) -> Data {
    let type: UInt8 = 42
    let identifier: UInt16 = 666

    var result = Data(count: MemoryLayout<ICMPHeader>.size)
    result.withUnsafeMutableBytes { (icmpPtr: UnsafeMutablePointer<ICMPHeader>) in
      icmpPtr.pointee.type = type 
      icmpPtr.pointee.code = 0
      icmpPtr.pointee.checksum = 0 
      icmpPtr.pointee.identifier = identifier.bigEndian
      icmpPtr.pointee.sequenceNumber = identifier.bigEndian 
    }
    result.append(payload)
    let checksum = result.withUnsafeBytes { (base: UnsafePointer<UInt8>) in
        return in_cksum(buffer: UnsafeBufferPointer(start: base, count: result.count))
    }
    result.withUnsafeMutableBytes { (icmpPtr: UnsafeMutablePointer<ICMPHeader>) in
        icmpPtr.pointee.checksum = checksum
    }

    return result
}

Note I’m not byte swapping

checksum
because the traditional IP checksum implementation returns the value big endian anyway.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"
@import Darwin;

#include <AssertMacros.h>

struct ICMPHeader { 
    uint8_t type; 
    uint8_t code; 
    uint16_t checksum;
    uint16_t identifier;
    uint16_t sequenceNumber;
};
typedef struct ICMPHeader ICMPHeader;
__Check_Compile_Time(sizeof(ICMPHeader) == 8);
__Check_Compile_Time(offsetof(ICMPHeader, type) == 0);
__Check_Compile_Time(offsetof(ICMPHeader, code) == 1);
__Check_Compile_Time(offsetof(ICMPHeader, checksum) == 2);
__Check_Compile_Time(offsetof(ICMPHeader, identifier) == 4);
__Check_Compile_Time(offsetof(ICMPHeader, sequenceNumber) == 6);