How to byte reverse NSData output in Swift the littleEndian way?
Asked Answered
P

3

8

I have this output from NSData: <00000100 84000c00 071490fe 4dfbd7e9>

So how could I byte reverse it in Swift and have this output: <00000001 0084000c 1407fe90 fb4de9d7>?

Photoplay answered 12/5, 2015 at 15:25 Comment(1)
is there anyway we can determine NSData is bigEndian or Little-Endian format?Algoid
B
14

This should work to swap each pair of adjacent bytes in the data. The idea is to interpret the bytes as an array of UInt16 integers and use the built-in byteSwapped property.

func swapUInt16Data(data : NSData) -> NSData {
    
    // Copy data into UInt16 array:
    let count = data.length / sizeof(UInt16)
    var array = [UInt16](count: count, repeatedValue: 0)
    data.getBytes(&array, length: count * sizeof(UInt16))
    
    // Swap each integer:
    for i in 0 ..< count {
        array[i] = array[i].byteSwapped // *** (see below)
    }

    // Create NSData from array:
    return NSData(bytes: &array, length: count * sizeof(UInt16))
}

If your actual intention is to convert data from an (external) big-endian representation to the host (native) byte order (which happens to be little-endian on all current iOS and OS X devices) then you should replace *** by

array[i] = UInt16(bigEndian: array[i])

Example:

var bytes : [UInt8] = [1, 2, 3, 4, 5, 6, 7, 8]
let data = NSData(bytes: &bytes, length: bytes.count)
print(data)
// <01020304 05060708>
print(swapUInt16Data(data))
// <02010403 06050807>

Update for Swift 3: The generic withUnsafeMutableBytes() methods allows to obtain a UnsafeMutablePointer<UInt16> to the bytes and modify them directly:

func swapUInt16Data(data : Data) -> Data {
    var mdata = data // make a mutable copy
    let count = data.count / MemoryLayout<UInt16>.size
    mdata.withUnsafeMutableBytes { (i16ptr: UnsafeMutablePointer<UInt16>) in
        for i in 0..<count {
            i16ptr[i] =  i16ptr[i].byteSwapped
        }
    }
    return mdata
}

Example:

let data = Data(bytes: [1, 2, 3, 4, 5, 6, 7, 8])
print(data as NSData) // <01020304 05060708>

let swapped = swapUInt16Data(data: data)
print(swapped as NSData) // <02010403 06050807>

Update for Swift 5, (fixing a deprecated warning):

func swapUInt16Data(data : Data) -> Data {
    var mdata = data // make a mutable copy
    let count = data.count / MemoryLayout<UInt16>.size
    mdata.withUnsafeMutableBytes { ptr in
        let i16ptr = ptr.assumingMemoryBound(to: UInt16.self)
        for i in 0..<count {
            i16ptr[i] =  i16ptr[i].byteSwapped
        }
    }
    return mdata
}
Brabazon answered 12/5, 2015 at 15:42 Comment(18)
Got this error: "Cannot invoke 'reverse' with an argument list of type '(UnsafeMutableBufferPointer<UInt8>)'" on line var reversedBytes = reverse(bytes).Photoplay
Well the first code I get that error and can't compile. The second code works, but does not correctly byte reverse because it reverses like backwards and what I want it if raw NSData output is 45 F4 67 C3, then I want: F4 45 C3 67. I think this is littleEndian.Photoplay
@Marco: Strange, it worked in my XCode 6.3.1. But I will double-check when I am at my computer again. – Endianness conversion is a different thing, that wasn't obvious from your question.Brabazon
@MartinR, where is the reverse() function defined? And documented? It's a function that takes an UnsafeMutableBufferPointer<UInt8> and returns another UnsafeMutableBufferPointer<UInt8> containing the reversed byte-stream?Kirtley
Sorry @Sulthan and Martin R. I have edited the question.Photoplay
@DuncanC: Unsafe(Mutable)BufferPointer conforms to CollectionType, that's why it can be reverse()'ed. The result is an array of the corresponding element type.Brabazon
Martin, thanks for explaining that. Now can you explain where you found byteSwapped? I can't find it in the SwiftDoc.org page the Swift standard library reference, or either of the Swift iBooks. The only way I can find it is to put it in source code in Xcode and command-click on it. I see that it's a computed property defined on UInt16, but I have never heard of it before today. Where do you learn about all this stuff?!?Kirtley
@DuncanC actually it's on the page swiftdoc.org/type/UInt16 at the instance variables section.Bevon
@MartinR Can you please update your great answer to Swift3 and with the "Data" object? (not NSData) Simple conversion tries didn't workOutweigh
@MatanGold: Done :)Brabazon
@MartinR I have a problem when I try to swap odd number of bytes. I have Data object of length 19 and after the swap I get 18 (dropping the last one) - Is there something can be done? (Here's Hex rep. of the odd data: "AA AA 11 12 0D 00 00 00 01 00 00 02 00 04 00 AC F0 44 3F")Outweigh
@MatanGold: Yes, the above code assumes that the number of bytes is even. What result would you expect for the last byte in your case?Brabazon
@MartinR just leave it as it is (This will keep original data length)Outweigh
@MatanGold: See update. The last version was actually wrong, it modified the first values only.Brabazon
@MartinR Perfect! Gr8!Outweigh
Thanx @MartinR, It Does Work for me, my only problem is it send a deprecated warning. "withUnsafeMutableBytes' is deprecated: use withUnsafeMutableBytes<R>(_: (UnsafeMutableRawBufferPointer) throws -> R) rethrows -> R instead". kindly can you write another update for swift 5 too, thanx again and good job.Hargrave
@AhmedEl-Bermawy: Updated for Swift 5.Brabazon
@MartinR, Fantastic Work.Hargrave
M
3

CoreFoundation has CFSwapInt32BigToHost and CFSwapInt32LittleToHost and other swap functions.

In swift3 it looks like this

struct FileHeader {
    var magicNumber: UInt32 = 0
    var count: UInt32 = 0
    var width: UInt32 = 0
    var height: UInt32 = 0

    static func create(data: Data) -> FileHeader {
        let structSize = MemoryLayout<FileHeader>.size
        assert(data.count >= structSize)
        var result = FileHeader()
        let nsdata = data as NSData
        nsdata.getBytes(&result, range: NSRange(location: 0, length: structSize))
        result.magicNumber = CFSwapInt32BigToHost(result.magicNumber)
        result.count = CFSwapInt32BigToHost(result.count)
        result.width = CFSwapInt32BigToHost(result.width)
        result.height = CFSwapInt32BigToHost(result.height)
        return result
    }
}
Mountie answered 24/9, 2016 at 14:51 Comment(1)
Copying the data to a FileHeader structure can be simplified to var result: FileHeader = data.withUnsafeBytes { $0.pointee } without the need for an intermediate NSData (compare https://mcmap.net/q/188471/-round-trip-swift-number-types-to-from-data). – Byte swapping can alternatively be done with "native" Swift methods, e.g. result.magicNumber = UInt32(bigEndian: result.magicNumber).Brabazon
R
0

For someone may want to restrict the byte pattern, it would be a solution:

func swap<U:IntegerType>(data:NSData,_ :U.Type) -> NSData{
    var length = data.length / sizeof(U)
    var bytes = [U](count: length, repeatedValue: 0)
    data.getBytes(&bytes, length: data.length)
    // since byteSwapped isn't declare in any protocol, so we have do it by ourselves manually.
    var inverse = bytes.enumerate().reduce([U](count: length, repeatedValue: 0)) { (var pre, ele) -> [U] in
        pre[length - 1 - ele.index] = ele.element
        return pre
    }
    return NSData(bytes: inverse, length: data.length)
}

for example:

swap(data:data,UInt8.self)
//before <0c20207b 17> 
//after <177b2020 0c>

swap(data:anotherData,UInt16.self)
//before <8e004c01 84008f05 0701>
//after <07018f05 84004c01 8e00>
Righteousness answered 10/10, 2015 at 6:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.