How to send message struct containing a string as NSData for Game Center - swift

I am trying to create a multiplayer game that will send moves between players using Game Center. I'm still learning a lot about programming, so please excuse me if my question is ill-formed. Also, I am not very familiar with Obj-C, so a Swift answer would be great.
In my toy program to try and teach myself, I am trying to follow the strategy used by Shayne Meyer using the GameKitHelper class here: https://github.com/shaynemeyer/SwiftCircuitRacer/tree/master/SwiftCircuitRacer
Using this approach, Shayne sends messages to other players online using structs sent as NSData. I am able to send integers (e.g., the ILoveYou message) but not messages that carry a string property (e.g., the Thanks message). In this latter case I get "Thread 1: EXC_BAD_ACCESS(code=1, address=0x78674100)" at the line "var messageThanks = UnsafePointer,MesssageThanks>(data.bytes).memory"
Eventually, I would like to send game moves that provide both strings and integers together. How does one send a message struct as NSData when properties also include a string? Secondly, I would be appreciative if someone could help me understand fundamentally what is going on when the data is packaged and how what UnsafePointer is doing as it related to sending data via Game Center.
Thank you.
Cliff
enum MessageType: Int {
case ILoveYou, Thanks
}
struct Message {
let messageType: MessageType
}
struct MessageILoveYou {
let message: Message
let messageSenderNumber: UInt32
}
struct MessageThanks {
let message: Message
let messageSenderName: String
let messageSenderNumber: UInt32
}
func sendILoveYou() {
println("sendILoveYou:")
let nameNumber = UInt32(56)
var message = MessageILoveYou(message: Message(messageType: MessageType.ILoveYou), messageSenderNumber: nameNumber)
let data = NSData(bytes: &message, length: sizeof(MessageILoveYou))
sendData(data)
}
func sendThanks() {
println("sendThanks:")
let nameString = "Don J"
let senderNumberInt = UInt32(88)
var message = MessageThanks(message: Message(messageType: MessageType.Thanks), messageSenderName: nameString, messageSenderNumber: senderNumberInt)
let data = NSData(bytes: &message, length: sizeof(MessageThanks))
sendData(data)
}
func matchReceivedData(match: GKMatch, data: NSData, fromPlayer player: String) {
println("matchReceivedData:")
var message = UnsafePointer<Message>(data.bytes).memory
if message.messageType == MessageType.ILoveYou {
println("messageType == ILoveYou")
let messageILoveYou = UnsafePointer<MessageILoveYou>(data.bytes).memory
iLoveYouThanksDelegate?.iLoveYouReceived(from: messageILoveYou.messageSenderNumber)
} else if message.messageType == MessageType.Thanks {
println("messageType == Thanks")
var messageThanks = UnsafePointer<MessageThanks>(data.bytes).memory
iLoveYouThanksDelegate?.thanksReceived(from: messageThanks.messageSenderName)
}
}
func sendData(data: NSData) {
var sendDataError: NSError?
let gameKitHelper = GameKitHelper.sharedInstance
if let multiplayerMatch = gameKitHelper.multiplayerMatch {
let success = multiplayerMatch.sendDataToAllPlayers(data, withDataMode: .Reliable, error: &sendDataError)
if !success {
if let error = sendDataError {
println("Error:\(error.localizedDescription)")
matchEnded()
}
}
}
}

The problem here is that when you create a String in Swift, it allocates a bit of memory itself, and then uses that memory to store the actual characters of the string. All that the string value really holds is some data representing a pointer to that memory and some other info (like how much memory has been allocated, so that it can be freed properly.
You can see this here:
let str = "This is quite a long string, certainly more than 24 bytes"
sizeofValue(str) // and yet this only returns 24
When you stuff variables into an NSData object, the initializer takes a pointer to the memory of the string variable that is holding those pointers, not the characters itself:
// only storing those 24 bytes, not the actual string
let data = NSData(bytes: &str, length: sizeofValue(str))
Note, the type of the bytes argument is UnsafePointer<Void>. This is an indication that you are heading into tricky territory.
Then, when you unmarshal the data at the other end, all your receiver is going to get is some pointers to random memory (sadly, memory on the other user’s device!)
If you want to put string values into an NSData object, you are going to need to marshal them first into raw data. For example, you could encode them into an array:
let data = Array(str.utf8).withUnsafeBufferPointer { buf in
NSData(bytes: buf.baseAddress, length: buf.count)
}
As it happens, since this is a common thing to want to do, there’s a method to do this directly:
let data = str.dataUsingEncoding(NSUTF8StringEncoding)
Then, to unpack the data, you can use NSString’s constructor from an NSData object:
let newStr = NSString(data: data, encoding: NSUTF8StringEncoding)
edit: if you wanted to encode more than just a string in a single NSData, you could do something along these lines… I should say, I’ve never had to do this myself so I’m in no way familiar with the standard practices for this, there could be much better techniques or helper classes/functions. Hopefully someone with more experience can edit to show how to do this properly :)
var type = MessageType.Thanks
// start the data with the type
let data = NSMutableData(bytes: &type, length: sizeofValue(type))
// then append the string
data.appendData(Array(str.utf8).withUnsafeBufferPointer { buf in
NSMutableData(bytes: buf.baseAddress, length: buf.count)
})
switch UnsafePointer<MessageType>(data.bytes).memory {
case .ILoveYou:
// ...
case .Thanks:
let str = NSString(data: data.subdataWithRange(NSMakeRange(1, data.length-1)), encoding: NSUTF8StringEncoding)
}

Related

How to accept any type of data from sockets in SwiftUI

I'm fairly new to swift programming. I am playing with sockets and am trying to understand this snippet:
socket.on("Hello") { [weak self] (data, ack) in
if let data = data[0] as? [String: String],
let rawMessage = data["msg"]
{
DispatchQueue.main.async {
self?.messages.append(rawMessage)
}
}
}
I can understand the first bit; socket.on("Hello"). It declares what to do when the socket receives "Hello". And I also know that self?.messages.append(rawMessage) is appending the message to a list. However, the format of the other parts is confusing to me. I am trying to modify it so I can accept any type of data. I think that if let data = data[0] as? [String:String] is filtering the data. But when I remove it and its parentheses, XCode throws error's at me. Could you please explain to me this code and how I should go about modifying it?

Issue with UserDefaults (converting data to array and back)

What I want to do:
I want to get an array from UserDefaults that I saved beforehand and append a custom object to it. Afterwards I want to encode it as a Data-type again and set this as the UserDefaults Key again.
My problem:
The encoding part is what is not working as intended for me.
It says: -[__SwiftValue encodeWithCoder:]: unrecognized selector sent to instance 0x60000011a540
But I do not know how to fix this.
Below is my code for more context:
do {
let decoded = defaults.object(forKey: "ExArray") as! Data
var exo = try NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(decoded) as! [Exerc]
exo.append(datas[indexPath.row])
let enco = try NSKeyedArchiver.archivedData(withRootObject: exo, requiringSecureCoding: false) <- Here is the error
defaults.set(enco, forKey: "ExArray")
} catch {
print("Error encoding custom object NOSEARCHO")
}
This is how Exerc looks:
struct Exerc: Codable {
var title: String
var exID: String
}
Seems like you are not using the archiver features, so why don't you just use the codable?
do {
let key = "ExArray"
let decoded = defaults.data(forKey: key)!
var exo = try JSONDecoder().decode([Exerc].self, from: decoded)
exo.append(datas[indexPath.row])
let enco = try JSONEncoder().encode(exo)
defaults.set(enco, forKey: key)
} catch {
print("Error encoding/decoding custom object NOSEARCHO", error)
}
It just a simple refactored MVP of the original code, but you can even work a bit on this and make it human readable right in the plist file!

How to use file descriptor to divert write-to-file in swift?

I would like to use some C code that uses a file descriptor.
Background is that I would like to read some data from cgraph library.
public extension UnsafeMutablePointer where Pointee == Agraph_t {
func saveTo(fileName: String) {
let f = fopen(cString(fileName), cString("w"))
agwrite(self,f)
fsync(fileno(f))
fclose(f)
}
}
I would like to have the file output, but without writing to a temp file. Hence, I would like to do something like this:
public extension UnsafeMutablePointer where Pointee == Agraph_t {
var asString: String {
let pipe = Pipe()
let fileDescriptor = UnsafeMutablePointer<Int32>.allocate(capacity: 1)
fileDescriptor.pointee = pipe.fileHandleForWriting.fileDescriptor
agwrite(self, fileDescriptor)
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let output = String(data: data, encoding: .utf8) {
return output
}
return ""
}
}
But it doesn't work, resulting in a EXC_BAD_ACCESS within agwrite(,). What do I need to do instead?
Many thanks in advance!
File descriptors and file pointers are not the same thing. It's confusing, and made even more frustrating by the fact that FILE * is really hard to Google because of the symbol.
You need to fdopen the file descriptor (pipe.fileHandleForWriting.fileDescriptor), to receive a FILE * (UnsafeMutablePointer<FILE> in Swift). This is what you then pass to agwrite.
It's important to fclose the file pointer when you're done writing to it, otherwise .readDataToEndOfFile() will never terminate. I made a helper function to ensure the fclose can't be forgetten. It's possible that agwrite closes the file pointer itself, internally. If that's the case, you should delete this code and just give it the result of fdopen, plain and simple.
import Foundation
public typealias Agraph_t = Int // Dummy value
public struct AGWriteWrongEncoding: Error { }
func agwrite(_: UnsafeMutablePointer<Agraph_t>, _ filePointer: UnsafeMutablePointer<FILE>) {
let message = "This is a stub."
_ = message.withCString { cString in
fputs(cString, stderr)
}
}
#discardableResult
func use<R>(
fileDescriptor: Int32,
mode: UnsafePointer<Int8>!,
closure: (UnsafeMutablePointer<FILE>) throws -> R
) rethrows -> R {
// Should prob remove this `!`, but IDK what a sensible recovery mechanism would be.
let filePointer = fdopen(fileDescriptor, mode)!
defer { fclose(filePointer) }
return try closure(filePointer)
}
public extension UnsafeMutablePointer where Pointee == Agraph_t {
func asString() throws -> String {
let pipe = Pipe()
use(fileDescriptor: pipe.fileHandleForWriting.fileDescriptor, mode: "w") { filePointer in
agwrite(self, filePointer)
}
let data = pipe.fileHandleForReading.readDataToEndOfFile()
guard let output = String(data: data, encoding: .utf8) else {
throw AGWriteWrongEncoding()
}
return output
}
}
let ptr = UnsafeMutablePointer<Agraph_t>.allocate(capacity: 1) // Dummy value
print(try ptr.asString())
Several other things:
Throwing an error is probably a better choice than returning "". Empty strings aren't a good error handling mechanism. Returning an optional would also work, but it's likely to always be force unwrapped, anyway.
readDataToEndOfFile is a blocking call, which can lead to a bad use experience. It's probably best that this code be run on a background thread, or use a FileHandle.readabilityHandler to asynchronously consume the data as it comes in.

AutoreleasingUnsafeMutablePointer crashes the app

I receive some data from internet and need to guess the encoding if it's not provided, so I use this function stringEncoding(for:encodingOptions:convertedString:usedLossyConversion:), and it requires passing AutoreleasingUnsafeMutablePointer for receiving the converted string, I wrote code like this:
var str = "Hello, playground"
func decode(data: Data) -> String? {
var covertedString = NSString()
let stringPointer = AutoreleasingUnsafeMutablePointer<NSString?>(&covertedString)
guard NSString.stringEncoding(for: data, encodingOptions: nil, convertedString: stringPointer, usedLossyConversion: nil) != 0 else {
return nil
}
return covertedString as String
}
let data = str.data(using: .utf8)!
decode(data: data)
While the covertedString I got out of the function call is correct, the app always crashes. Any idea why AutoreleasingUnsafeMutablePointer is make it crashes? I tried to not passing convertedString, then it's not crashing any more, so looks like it's the root case. Any idea why it's crashing?
I am using Xcode Version 10.1 (10B61), with Swift 4
In your particular case the problem is that you have created an NSString, but then taken a pointer to a NSString?, which is a different thing.
But that doesn't really matter here. You don't create AutoreleasingUnsafeMutablePointer directly (or generally any kind of UnsafePointer). They're not promised to be valid by the time you use them. Instead, you create them implicitly using &.
func decode(data: Data) -> String? {
var convertedString: NSString? = "" // <- Make sure to make this optional
guard NSString.stringEncoding(for: data,
encodingOptions: nil,
convertedString: &convertedString, // <- Use &
usedLossyConversion: nil) != 0
else {
return nil
}
return convertedString as String?
}

Encrypted NSData to NSString in obj-c?

I have an iPhone app which encrypts an inputted NSString using CCCrypt (AES256) and a plaintext key. The string and key are given to the encryption method which returns an NSData object.
Requesting [data description] where 'data' is the encrypted string data gives an NSString like: "<0b368353 a707e7de 3eee5992 ee69827e e3603dc2 b0dbbc0b 861ca87d f39ce72a>" but when I try to convert that to an NSString, I get "(null)".
I need to return an NSString to the user, which can be used to decrypt back to the original string using the same plaintext key. If the 'description' property of the NSData object can return a string, is there any way I can produce an NSString from the NSData object without getting "(null)"?
UPDATE: Thanks to Quinn, who suggests using Base64 encoding to produce the muddled string. From what I understand, Base64 encoding does not simply swap characters, but the character exchange depends on the position, so that's fine.
My only concern is that I want to be able to encrypt the message with a 'passphrase', and require the identical passphrase to be entered when the muddled string needs to be decoded - can anybody suggest ways to implement this?
First off, DO NOT use -[NSData description] to create an NSString for such purposes. (It's best to treat -description as debugging output. I apologize if my previous answer misled you, I was merely printing the description to demonstrate that the NSData can be encrypted and decrypted.) Instead, use NSString's -dataUsingEncoding: and -initWithData:encoding: methods to convert between NSData and NSString. Even with these, note that AES-encrypted data will probably not translate well into strings as-is — some byte sequences just won't play nicely, so it's a good idea to encode the data before creating the string.
I'd suggest you try Base64 encoding the NSData, since Base64 data can always be represented as an ASCII string. (Of course, when you do that, you'll have to decode from Base64 before decrypting.)
Here are some helpful resources...
Colloquy has some code that does encoding/decoding on NSData (header and implementation)
Google Toolbox for Mac has similar functionality (header and implementation)
A Cocoa With Love blog post on the topic.
A CocoaDev.com wiki page on the topic.
Edit: I was assuming you'd combine this with my answer to your previous question on AES encryption of NSString objects. Encoding data as Base64 doesn't place any restrictions on the data itself — it can certainly be AES-enrypted data itself. Here's what to do if you just want string input and output:
Encryption
Provide the NSString to be encrypted, and the passphrase to use for encrypting.
Convert the string to an NSData and perform AES encryption on it (see previous question).
Base64-encode the NSData, then create and return and NSString of the encoded output.
Decryption
Provide the encrypted and encoded string, and the passphrase to use for decrypting.
Create an NSData from the first string, then Base64-decode the data.
Perform AES decryption on the data, then create and return an NSString.
It's really just a matter of chaining the two parts together and performing them in reverse on the way out. From my previous answer, you can modify encryptString:withKey: to perform the last step and return a string, and change decryptData:withKey: to be decryptString:withKey: and accept two strings. It's pretty straightforward.
I have put together a complete bundle of categories for NSData and NSString to provide AES256 encryption for strings.
Please see my answer on the 'original' question for more details.
I have similar requirement where I need to encrypt all the strings when user enters the password to enter to app so that those sensitive strings doesn't remain unencrypted all the time. So I have to keep those strings encrypted and decrypt as an when require only.
It was a simple requirement and I wanted to keep it light. So I have created a small Obfuscator using lot of useful info shared by #RobNapier in one his blog. It might help for those who are looking a lightweight solution with lot of juicy comments.
import Foundation
import CommonCrypto
// A thin wrapper around interfacing
public enum CrypticAlgo {
case AlgoAES
case AlgoDES
func blockSize() -> Int {
switch self {
case .AlgoAES:
return kCCBlockSizeAES128
case .AlgoDES:
return kCCBlockSizeDES
}
}
func keySize() -> size_t {
switch self {
case .AlgoAES:
return kCCKeySizeAES128
case .AlgoDES:
return kCCKeySizeDES
}
}
func algo() -> UInt32 {
switch self {
case .AlgoAES:
return CCAlgorithm(kCCAlgorithmAES)
case .AlgoDES:
return CCAlgorithm(kCCAlgorithmDES)
}
}
}
public final class MGObfuscate {
private var ivData: [UInt8]?
private var derivedKey: Data?
private let crypticAlgo: CrypticAlgo
public init(password: String, salt: String, algo: CrypticAlgo) {
//Quickly get the data to release the password string
let passwordData = password.data(using: .utf8)!
//
// Rounds require for 1 sec delay in generating hash.
// Salt is a public attribute. If attacker somehow get the drivedKey and try to crack
// the password via brute force, The delay due to Rounds will make it frustrating
// to get actual password and deter his/her efforts.
//
let rounds = CCCalibratePBKDF(CCPBKDFAlgorithm(kCCPBKDF2), password.count,
salt.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), Int(CC_SHA256_DIGEST_LENGTH), 1000)
let saltData = salt.data(using: .utf8)!
derivedKey = MGObfuscate.derivedKey(for: passwordData,
saltData: saltData, rounds: rounds)
self.crypticAlgo = algo
var ivData = [UInt8](repeating: 0, count: algo.blockSize())
// Random criptographically secure bytes for initialisation Vector
let rStatus = SecRandomCopyBytes(kSecRandomDefault, ivData.count, &ivData)
self.ivData = ivData
// print(ivData)
guard rStatus == errSecSuccess else {
fatalError("seed not generated \(rStatus)")
}
}
#inline(__always) private static func derivedKey(for passwordData: Data, saltData: Data, rounds: UInt32) -> Data {
var derivedData = Data(count: Int(CC_SHA256_DIGEST_LENGTH))
let result = derivedData.withUnsafeMutableBytes { (drivedBytes: UnsafeMutablePointer<UInt8>?) in
passwordData.withUnsafeBytes({ (passwordBytes: UnsafePointer<Int8>!) in
saltData.withUnsafeBytes({ (saltBytes: UnsafePointer<UInt8>!) in
CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordBytes, passwordData.count, saltBytes, saltData.count, CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256), rounds, drivedBytes, Int(CC_SHA256_DIGEST_LENGTH))
})
})
}
if kCCSuccess != result {
fatalError("failed to generate hash for password")
}
return derivedData
}
private func runCryptic(operation: Int, inputData: Data, keyData: Data, ivData: Data) -> Data {
let cryptLength = size_t(inputData.count + crypticAlgo.blockSize())
var cryptData = Data(count: cryptLength)
let keyLength = crypticAlgo.keySize()
var bytesProcessed: size_t = 0
let cryptStatus = cryptData.withUnsafeMutableBytes {cryptBytes in
inputData.withUnsafeBytes { dataBytes in
keyData.withUnsafeBytes { keyBytes in
ivData.withUnsafeBytes{ ivBytes in
CCCrypt(CCOperation(operation),
crypticAlgo.algo(),
CCOptions(kCCOptionPKCS7Padding),
keyBytes, keyLength,
ivBytes,
dataBytes, inputData.count,
cryptBytes, cryptLength,
&bytesProcessed)
}
}
}
}
if cryptStatus == CCCryptorStatus(kCCSuccess) {
cryptData.removeSubrange(bytesProcessed..<cryptData.count)
} else {
fatalError("Error: \(cryptStatus)")
}
return cryptData
}
public func encriptAndPurge(inputString: inout String?) -> Data? {
if let inputdata = inputString?.data(using: .utf8) {
inputString = nil
return runCryptic(operation: kCCEncrypt, inputData: inputdata, keyData: derivedKey!, ivData: Data(bytes: ivData!))
}
return nil
}
public func encript(inputString: String) -> Data {
let inputdata = inputString.data(using: .utf8)!
return runCryptic(operation: kCCEncrypt, inputData: inputdata, keyData: derivedKey!, ivData: Data(bytes: ivData!))
}
public func decript(data: Data, result: (String) -> Void) {
let data = runCryptic(operation: kCCDecrypt, inputData: data, keyData: derivedKey!, ivData: Data(bytes: ivData!))
result(String(data: data, encoding: .utf8)!)
}
public func purge() {
ivData = nil
derivedKey = nil
}
}