Search code examples
iosswift3icmpunsafe-pointersunsafemutablepointer

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))

Solution

  • I have found the answer in XCode 8.1 Swift release notes.

    After making the changes in struct object icmpPtr I have changed it back to buffer pointer.

    var byteBuffer = [UInt8]()
    withUnsafeBytes(of: &icmpPtr) {
        (bytes: UnsafeRawBufferPointer) in byteBuffer += bytes
    }
    package.replaceBytes(in: NSMakeRange(0, byteBuffer.count), withBytes: byteBuffer)
    

    So instead of creating the new Data object, I have replaced the bytes and it worked like a charm.

    As per documentation:

    XCode 8.1 Release Notes: https://developer.apple.com/library/content/releasenotes/DeveloperTools/RN-Xcode/Introduction.html Swift Section:

    A new withUnsafeBytes(of:) function exposes a value's in-memory representation as an UnsafeRawBufferPointer. This example copies a heterogenous struct into a homogeneous array of bytes:

    struct Header {
     var x: Int
     var y: Float
    }
    
    var header = Header(x: 0, y: 0.0)
    var byteBuffer = [UInt8]()
    
    withUnsafeBytes(of: &header) {
        (bytes: UnsafeRawBufferPointer) in byteBuffer += bytes
    }
    

    A new Array.withUnsafeBytes method exposes an array's underlying buffer as an UnsafeRawBufferPointer. This example copies an array of integers into an array of bytes:

    let intArray = [1, 2, 3]
    var byteBuffer = [UInt8]()
    
    intArray.withUnsafeBytes {
        (bytes: UnsafeRawBufferPointer) in byteBuffer += bytes
    }
    

    So the final implementation is

    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);
           }
    
           var byteBuffer = [UInt8]()
           withUnsafeBytes(of: &icmpPtr) {
             (bytes: UnsafeRawBufferPointer) in byteBuffer += bytes
           }
           packet.replaceBytes(in: NSMakeRange(0, byteBuffer.count), withBytes: byteBuffer)
    
       }
       return packet
    }