Convert NSData bytes to custom object - ios

I am starting a project to create an iOS app to communicate with a device over BLE. Being a new effort, I am trying to do this is Swift if possible. The interface uses GATT and an existing set of custom message structures. I get to a point where I have the data from BLE in an NSData object. I'd like to cast it or directly convert it to my message structure in a fairly generic way.
I know that I can extract the data by hand either directly from the byte array from the NSData object or using an NSInputStream. While that works, it could be a maintenance issue and the interface has a number of different messages in it.
Is there an easier ways to do this?
I'd be willing to create the message structures in Objective-C and do the casting there, but my knowledge of Objective-C is not much better than my knowledge of Swift.
Some sample code of what I've been playing in my playground is shown below. It all works as expected.
func getBytesFromNSData(data: NSData, start: Int) -> [UInt8] {
let count = data.length / sizeof(UInt8)
let remaining = count - start
let range = NSMakeRange(start, remaining )
var dataArray = [UInt8](count: remaining, repeatedValue: 0)
data.getBytes(&dataArray, range: range)
return dataArray
}
class TestObject {
var a: Byte
var b: Byte
init() {
a = 0x01
b = 0x02
}
init(data: NSData) {
let dataBytes = getBytesFromNSData(data, 0)
a = Byte(dataBytes[0])
b = Byte(dataBytes[1])
}
func populateFromStream(data: NSData) {
var stream = NSInputStream(data: data)
stream.open()
var bytesRead = stream.read(&a, maxLength: 1)
println("\(bytesRead)")
bytesRead = stream.read(&b, maxLength: 1)
println("\(bytesRead)")
}
func toArray() -> [Byte] {
var result = [Byte](count: 2, repeatedValue: 0)
result[0] = a
result[1] = b
return result
}
}
let test = TestObject()
let testArray = test.toArray()
let length = testArray.count
let testData = NSData(bytes: testArray, length: length)
println("\(testData)")
let testIn = [ Byte(0x0d), Byte(0x0e) ]
let testDataIn = NSData(bytes: testIn, length: testIn.count)
println("\(testDataIn)")
let testConstructor = TestObject(data: testDataIn)
var testObject = TestObject()
testObject.populateFromStream(testDataIn)

I found a method that is fairly generic that may work is some cases.
Create an Objective-C riding header
Create the data structure as an Objective-C structure
Import the header with the data structure into the bridging header
Assuming that you have a struct called Foo and an NSData object called rawData:
Use the following code to get an cast a pointer.
let structureSize = sizeof(Foo)
var myObject = UnsafeMutablePointer<Foo>.alloc(1).memory
rawData.getbytes(&myObject, length: structureSize)
This will not work in all instances and unfortunately does work in my particular case. The specific problems I have found are:
The Objective-C structure is word aligned. If your structure is not properly aligned to work boundaries, you may have a size that is incorrect. (something I ran into in my particular interface)
If you and dealing with a system that doesn't send the data in the same order you are expecting, this will not handle any byte order conversion, that would still need to be done and the structure would possibly need to be reordered to compensate. That work might negate any saving from this method.
This is the most concise method I have found if it happens to work with your particular message formats.

Related

Sending bytes on outputstream with Swift

I'm working with a custom network protocol that requires a precise bytes sequence to do a sort of handshake, as example the first call should send a body like:
0003joy that, translated in a [UInt8] should be [0x00,0x00,0x00,0x03,0x6a,0x6f,0x79]
(please note that the first 4 numbers should not be converted to char... I'm sending the numeric value, as per protocol request)
I'm trying to send this sequence to an outputstream but I'm wondering if the steps I'm following are correct, here is my code... do you see anything strange that might prevent this sequence to reach the server?
// Create Bytes sequence
let bytes:[UInt8] = [0x00,0x00,0x00,0x03,0x6a,0x6f,0x79]
// Convert Bytes array do Data
let dt = Data(bytes)
// Send Data to stream
_ = dt.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
outputStream.write(pointer, maxLength: dt.count)
}
Also, do I need to convert the bytes sequence to Data? is there a way to send bytes sequence directly into the socket without converting it into Data?
I don't see anything wrong with your code but the conversion to Data is not needed:
bytes.withUnsafeBytes {
guard let pointer = $0.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
return
}
outputStream.write(pointer, maxLength: bytes.count)
}
since [UInt8] conforms to ContiguousBytes.
Actually, the following is also possible:
bytes.withUnsafeBufferPointer {
guard let baseAddress = $0.baseAddress else { return }
outputStream.write(baseAddress, maxLength: bytes.count)
}

NSNetService dictionaryFromTXTRecord fails an assertion on invalid input

The input to dictionary(fromTXTRecord:) comes from the network, potentially from outside the app, or even the device. However, Apple's docs say:
... Fails an assertion if txtData cannot be represented as an NSDictionary object.
Failing an assertion leaves the programmer (me) with no way of handling the error, which seems illogic for a method that processes external data.
If I run this in Terminal on a Mac:
dns-sd -R 'My Service Name' _myservice._tcp local 4567 asdf asdf
my app, running in an iPhone, crashes.
dictionary(fromTXTRecord:) expects the TXT record data (asdf asdf) to be in key=val form. If, like above, a word doesn't contain any = the method won't be able to parse it and fail the assertion.
I see no way of solving this problem other than not using that method at all and implementing my own parsing, which feels wrong.
Am I missing something?
Here's a solution in Swift 4.2, assuming the TXT record has only strings:
/// Decode the TXT record as a string dictionary, or [:] if the data is malformed
public func dictionary(fromTXTRecord txtData: Data) -> [String: String] {
var result = [String: String]()
var data = txtData
while !data.isEmpty {
// The first byte of each record is its length, so prefix that much data
let recordLength = Int(data.removeFirst())
guard data.count >= recordLength else { return [:] }
let recordData = data[..<(data.startIndex + recordLength)]
data = data.dropFirst(recordLength)
guard let record = String(bytes: recordData, encoding: .utf8) else { return [:] }
// The format of the entry is "key=value"
// (According to the reference implementation, = is optional if there is no value,
// and any equals signs after the first are part of the value.)
// `ommittingEmptySubsequences` is necessary otherwise an empty string will crash the next line
let keyValue = record.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false)
let key = String(keyValue[0])
// If there's no value, make the value the empty string
switch keyValue.count {
case 1:
result[key] = ""
case 2:
result[key] = String(keyValue[1])
default:
fatalError()
}
}
return result
}
I'm still hoping there's something I'm missing here, but in the mean time, I ended up checking the data for correctness and only then calling Apple's own method.
Here's my workaround:
func dictionaryFromTXTRecordData(data: NSData) -> [String:NSData] {
let buffer = UnsafeBufferPointer<UInt8>(start: UnsafePointer(data.bytes), count: data.length)
var pos = 0
while pos < buffer.count {
let len = Int(buffer[pos])
if len > (buffer.count - pos + 1) {
return [:]
}
let subdata = data.subdataWithRange(NSRange(location: pos + 1, length: len))
guard let substring = String(data: subdata, encoding: NSUTF8StringEncoding) else {
return [:]
}
if !substring.containsString("=") {
return [:]
}
pos = pos + len + 1
}
return NSNetService.dictionaryFromTXTRecordData(data)
}
I'm using Swift 2 here. All contributions are welcome. Swift 3 versions, Objective-C versions, improvements, corrections.
I just ran into this one using Swift 3. In my case the problem only occurred when I used NetService.dictionary(fromTXTRecord:) but did not occur when I switched to Objective-C and called NSNetService dictionaryFromTXTRecord:. When the Objective-C call encounters an entry without an equal sign it creates a key containing the data and shoves it into the dictionary with an NSNull value. From what I can tell the Swift version then enumerates that dictionary and throws a fit when it sees the NSNull. My solution was to add an Objective-C file and a utility function that calls dictionaryFromTXTRecord: and cleans up the results before handing them back to my Swift code.

use fscanf() function in swift code

In objective c, I use fscanf to read stream from file and assign the value to variables:
int count;
char type[5];
fscanf(myFile, “count is %d, type is %4s ”, &count, type)
I want to do the same thing in swift code, I tried:
//ERROR: Type annotation missing in pattern
//What type should I use for `count`?
var count
//ERROR: consecutive statement on a line must be separated by ‘;’
var type[5] : char
fscanf(myFile, “count is %d, type is %4s ”, &count, type)
But I got compiler errors showing above. What is the correct way to use fscanf in swift ?
If you know any swift way to achieve the same thing (without using fscanf), it would be great too!
I recommend you use Foundation framework solution for reading/writing file data. A sample code to read contents of files which I used in my app to stream file into NSData:
if let fileHandle = NSFileHandle(forReadingAtPath: "path/to/file") {
fileHandle.seekToFileOffset(0)
var data = fileHandle.readDataOfLength(5)
var chars = [UInt8](count: 5, repeatedValue: 0)
data.getBytes(&chars, length: 5)
fileHandle.closeFile()
}
In case you need read Int64 data from file at a specific location:
if let fileHandle = NSFileHandle(forReadingAtPath: "path/to/file") {
fileHandle.seekToFileOffset(0)
var data = fileHandle.readDataOfLength(500)
var intFetched: Int64 = 0
let location = 100 // start at 101st character of file
data.getBytes(&intFetched, range: NSMakeRange(location, 8))
println(intFetched.littleEndian)
fileHandle.closeFile()
}

Random unique string generation to use as nonce (oauth)

I am trying to use the UUID to generate as a nonce to be use for Twitter reverse authentication. But apparently the UUID is not a good choice. So how can I generate a unique random string every time stripping out all non-word characters, taking care that it gets release from memory after use. The following code crashes.
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var nonce: CFStringRef = CFUUIDCreateString(nil, uuid)
CFRelease(uuid)
println("createdNonce:\(nonce)")
EDIT:
I am on xcode6 beta2, and i can't debug, and xocde crashes, any chance it gets. So well, the CFRelease part is crashing for me. Once I remove, it seems to work fine, but I dont know if this will create a memory leak.
As to why UUID's might not be a good choice to use for nonce it seems is because, UUID's are not made of true random bits, referring this discussion here: https://github.com/aws/aws-sdk-ios/issues/30
A more correct way of generation a nonce would probably be to generate random bytes using a cryptographic RNG. iOS just happens to have such a thing:
var s = NSMutableData(length: 32)
SecRandomCopyBytes(kSecRandomDefault, UInt(s.length), UnsafePointer<UInt8>(s.mutableBytes))
// s is now a NSData containing 32 random bytes
Then convert to a string using whatever format the API suggests (probably Base64), e.g.
let base64str = s.base64EncodedStringWithOptions(0)
EDIT: The approach above seems to be the one Twitter uses in the docs. See here. I can't say if UUIDS are more easily predicted. It depends on the method they are generated. SecRandomCopyBytes seems to be used for cryptographic purposes though, so it should be safe to use.
With SwiftUI 5.x and CryptoKit, it is easy:
import CryptoKit
let nonce = ChaChaPoly.Nonce.init()
in use:
let encryptedContent = try! ChaChaPoly.seal(inputBuffer, using: symmetricKey, nonce: ChaChaPoly.Nonce.init()).combined
One example that's provided by Firebase's Authenticating Using Apple tutorial:
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
Your code is crashing because you are calling CFRelease.In swift there is no need to call CFRelease.From swift guide
Core Foundation objects returned from annotated APIs are automatically
memory managed in Swift—you do not need to invoke the CFRetain,
CFRelease, or CFAutorelease functions yourself.
var uuid: CFUUIDRef = CFUUIDCreate(nil)
var nonce: CFStringRef = CFUUIDCreateString(nil, uuid)
//Remove this swift will manage the memory managment.This line is causing crash
//CFRelease(uuid)
println("createdNonce:\(nonce)")
this code will work fine.No this will not create memory leak swift will manage that
Here is an answer that works in Swift 4:
func generateNonce(lenght: Int) throws -> Data {
let nonce = NSMutableData(length: lenght)
let result = SecRandomCopyBytes(kSecRandomDefault, nonce!.length, nonce!.mutableBytes)
if result == errSecSuccess {
return nonce! as Data
} else {
throw Error
}
}
This might not be a perfect solution but want to suggest it because it will take a lot of Twitter reverse auth madness for you..
Try to use cocoapod TWReverseAuth
Here is a easier way to do this then:
var temp = NSUUID.UUID().UUIDString
var nonce = temp.stringByReplacingOccurrencesOfString("-", withString: "")
println("createdNonce:\(nonce)")

Swift - converting from ConstUnsafePointer<()>

I'm on beta 3. Consider the following Objective-C line:
const uint8_t *reportData = [data bytes];
where data is a NSData object.
How would this line be re-written in Swift?
data.bytes is of type ConstUnsafePointer<()>, and while there's plenty of documentation on how to create a pointer type in Swift, there isn't much info on how to work with them.
edit:
To add some context, I'm trying to port Apple's HeartRateMonitor sample code to Swift. This code interacts with BLE heart rate monitors. This code I'm working on translates the data received by the Bluetooth system into an int for use in the UI. The data received from BT is expected to be an array of uints, element 0 is used to check for a flag and element 1 contains the value.
Here's the same Objective-C line in context:
const uint8_t *reportData = [data bytes];
uint16_t bpm = 0;
if ((reportData[0] & 0x01) == 0)
{
/* uint8 bpm */
bpm = reportData[1];
}
What you were looking for was how to convert NSData to an array of UInt8. Here's how.
import Foundation
let path = "/etc/csh.cshrc" // something existent
let data = NSData(contentsOfFile: path)
var aofb = [UInt8](count:data.length, repeatedValue:0)
data.getBytes(&aofb, length:data.length)
for c in aofb {
let s = UnicodeScalar(Int(c)).escape(asASCII:true)
println("\(c):\(s)")
}
Just built following code (Note code below works on Beta 3, ConstUnsafePointer<()> needs to be changed to COpaquePointer in order to work on Beta 2, please see edit history for more information)
var dataPath = NSBundle.mainBundle().pathForResource("TestData", ofType: "") // What I have in TestData is "GREETINGS WORLD"
var originalData = NSData(contentsOfFile: dataPath)
var dataLength = originalData.length
println("original data: \(originalData)") // Output original data
// Data to bytes
var reportBytes: ConstUnsafePointer<()> = originalData.bytes
var bytesToString = NSString(bytes: reportBytes, length: dataLength, encoding: NSUTF8StringEncoding)
println("string from bytes: \(bytesToString)")
// Bytes to data
var bytesToData = NSData(bytes: reportBytes, length: dataLength)
println("data from bytes: \(bytesToData)")
Console log
original data: <47524545 54494e47 5320574f 524c44>
string from bytes: GREETINGS WORLD
data from bytes: <47524545 54494e47 5320574f 524c44>
Also found this may help
ConstUnsafePointer<T>
/// This type stores a pointer to an object of type T. It provides no
/// automated memory management, and therefore the user must take care
/// to allocate and free memory appropriately.
Hope this shed light.
Looking at handling bluetooth heart rate monitors in Swift now I found the simplest way to get the NSData byte values to UInt8 format:
let bytes = UnsafePointer<UInt8>(data.bytes)
if bytes[0] & 0x01 == 0 {
NSLog("BPM \(bytes[1]")
}

Resources