Convert Int to UnsafePointer<UInt8> - swift

I'm creating a TCP client in swift for an app on iOS device. I'm using NSOuputStream to send data on the server.
Our protocol is JSON type and we have to send first the length of the json (unsigned Int 32 bits) and then the JSON content (as UTF8 string).
Every thing work fine, except that I cannot find how to write my Int using write(_:maxLength:) function of NSOutputStream..
To convert my JSON String into UnsafePointer<UInt8> I use the following
//var json: String
let encodedData = [UInt8](json.utf8)
let result = outputStream?.write(encodedData, maxLength: encodedData.count)
Does someone know how to do it for an Int ?
Thanks for your help !

You should be able to do something like this:
var int = 100
let result = withUnsafePointer(&int) {
outputStream?.write(UnsafePointer($0), maxLength: sizeof(Int))
}
EDIT: To read your Int you can do
func getInt(ptr: UnsafePointer<UInt8>) -> Int {
return UnsafePointer(ptr).memory
}

Swift 4:
var int = 100
let result = withUnsafePointer(to: & int) {
let unsafePointer8 = $0.withMemoryRebound(to: UInt8.self, capacity: 1, {$0})
outputStream?.write(unsafePointer8, maxLength: MemoryLayout<Int64>.size)
}

Related

Swift 4 Int32 to [UInt8]

I am trying to get the bytes from an integer into an [UInt8] to send them over a wire protocol. While I found answers that work for Swift 2/3, none of the solutions work for Swift 4.
The following snippet works to encode a message for small message sizes (just the raw string data prepended with a network byte order Int32 size):
func send(message: String) {
let messageSize = message.utf8.count
let encodedMessageSize = Int32(messageSize).bigEndian
let frameSize = messageSize + 4
var buffer: [UInt8] = Array()
buffer.append(0)
buffer.append(0)
buffer.append(0)
buffer.append(UInt8(messageSize))
buffer.append(contentsOf: message.utf8)
outputStream.write(buffer, maxLength: frameSize)
}
I have also tried using raw pointers directly, but cannot get anything to work for Swift 4 along that avenue either.
The overall tasks is to encode and frame messages that consist of integers and strings. The encoding converts everything to strings and adds a null at the end of each string. The framing simply prepends the message with a network byte order Int32 size. I cannot change the protocol, but am willing to consider other approaches to achieving this end.
cheers,
[EDIT] Updated code using #MartinR's code (with #Hamish's suggestion). Also made some progress of the overall task in the mean time.
func encodeMessagePart(_ message: String) -> [UInt8] {
var buffer: [UInt8] = Array(message.utf8)
buffer.append(0)
return buffer
}
func encodeMessagePart(_ message: Int) -> [UInt8] {
return encodeMessagePart("\(message)")
}
func frameMessage(_ buffer: [UInt8]) -> [UInt8] {
let bufferSize = buffer.count
var encodedBufferSize = Int32(bufferSize).bigEndian
let encodedBufferSizeData = withUnsafeBytes(of: &encodedBufferSize) { Data($0) }
var frame: [UInt8] = Array()
frame.append(contentsOf: encodedBufferSizeData)
frame.append(contentsOf: buffer)
return frame
}
func sendMessage(_ buffer: [UInt8]) {
let frame = frameMessage(buffer)
outputStream.write(frame, maxLength: frame.count)
}
func sendMessage(_ message: String) {
let encodedPart = encodeMessagePart(message)
sendMessage(encodedPart)
}
// func sendMessage(_ messages: Encodable...) {
// var buffer: [UInt8] = Array()
// for message in messages {
// let b = encodeMessagePart(message)
// buffer.append(contentsOf: b)
// }
// sendMessage(buffer)
// }
You can create a Data value from the integer with
let encodedMessageSize = Int32(messageSize).bigEndian
let data = withUnsafeBytes(of: encodedMessageSize) { Data($0) }
(In Swift versions before 4.2 you'll have to write
var encodedMessageSize = Int32(messageSize).bigEndian
let data = withUnsafeBytes(of: &encodedMessageSize) { Data($0) }
instead.)
The data can then be appended to the array with
buffer.append(contentsOf: data)
Alternatively you can use a data buffer instead of an array:
func send(message: String) {
let messageSize = message.utf8.count
let encodedMessageSize = Int32(messageSize).bigEndian
var data = withUnsafeBytes(of: encodedMessageSize) { Data($0) }
data.append(Data(message.utf8))
let amountWritten = data.withUnsafeBytes { [count = data.count] in
outputStream.write($0, maxLength: count)
}
}
Finally note that that the write() method might write less bytes than
provided (e.g. on network connections), so you should always check
the return value.

Append Int to Data in Swift 3

I am writing datagram for sending it to a server via UDP socket. How can I append Int to the end of a datagram (already composed Data)?
You can use the
public mutating func append<SourceType>(_ buffer: UnsafeBufferPointer<SourceType>)
method of Data. You probably also want to convert the value
to network (big-endian) byte order when communicating between
different platforms, and use fixed-size types like (U)Int16,
(U)Int32, or (U)Int64.
Example:
var data = Data()
let value: Int32 = 0x12345678
var beValue = value.bigEndian
data.append(UnsafeBufferPointer(start: &beValue, count: 1))
print(data as NSData) // <12345678>
Update for Swift 4/5:
let value: Int32 = 0x12345678
withUnsafeBytes(of: value.bigEndian) { data.append(contentsOf: $0) }
The intermediate variable is no longer needed.
Better way to do it:
var data = Data()
let value: Int32 = 0x12345678
var bigEndianVal = value.bigEndian
withUnsafePointer(to: &bigEndianVal) {
data.append(UnsafeBufferPointer(start: $0, count: 1))
}

Swift structs to NSData and back

I have a struct containing a struct and an NSObject that I want to serialize into an NSData object:
struct Packet {
var name: String
var index: Int
var numberOfPackets: Int
var data: NSData
}
var thePacket = Packet(name: name, index: i, numberOfPackets: numberOfPackets, data: packetData)
How do I best serialize the Packet into an NSData, and how do I best deserialize it?
Using
var bufferData = NSData(bytes: & thePacket, length: sizeof(Packet))
of only gives me the pointers of name and data. I was exploring NSKeyedArchiver, but then I'd have to make Packet an object, and I'd prefer to keep it a struct.
Cheers
Nik
Not really getting any feedback, this is the solution I ended up with:
Make encode() and decode() functions for my struct
Change Int to Int64 so the Int has the same size on 32-bit and 64-bit platforms
Have an intermediate struct (ArchivedPacket) that has no String or Data, but only Int64
Here is my code, I would be very grateful for your feedback, especially if there are less cumbersome ways to do this:
public struct Packet {
var name: String
var index: Int64
var numberOfPackets: Int64
var data: NSData
struct ArchivedPacket {
var index : Int64
var numberOfPackets : Int64
var nameLength : Int64
var dataLength : Int64
}
func archive() -> NSData {
var archivedPacket = ArchivedPacket(index: Int64(self.index), numberOfPackets: Int64(self.numberOfPackets), nameLength: Int64(self.name.lengthOfBytesUsingEncoding(NSUTF8StringEncoding)), dataLength: Int64(self.data.length))
var metadata = NSData(
bytes: &archivedPacket,
length: sizeof(ArchivedPacket)
)
let archivedData = NSMutableData(data: metadata)
archivedData.appendData(name.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)!)
archivedData.appendData(data)
return archivedData
}
func unarchive(data: NSData!) -> Packet {
var archivedPacket = ArchivedPacket(index: 0, numberOfPackets: 0, nameLength: 0, dataLength: 0)
let archivedStructLength = sizeof(ArchivedPacket)
let archivedData = data.subdataWithRange(NSMakeRange(0, archivedStructLength))
archivedData.getBytes(&archivedPacket)
let nameRange = NSMakeRange(archivedStructLength, Int(archivedPacket.nameLength))
let dataRange = NSMakeRange(archivedStructLength + Int(archivedPacket.nameLength), Int(archivedPacket.dataLength))
let nameData = data.subdataWithRange(nameRange)
let name = NSString(data: nameData, encoding: NSUTF8StringEncoding) as! String
let theData = data.subdataWithRange(dataRange)
let packet = Packet(name: name, index: archivedPacket.index, numberOfPackets: archivedPacket.numberOfPackets, data: theData)
return packet
}
}
Swift 5
If you are on Apple platforms, use Codable now. See documentation.
Swift 3
This is an unaltered copy-paste from a Playground in Xcode 8.2.1 that works. It is a bit simpler than other answers.
import Foundation
enum WhizzoKind {
case floom
case bzzz
}
struct Whizzo {
let name: String
let num: Int
let kind:WhizzoKind
static func archive(w:Whizzo) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Whizzo>.stride)
}
static func unarchive(d:Data) -> Whizzo {
guard d.count == MemoryLayout<Whizzo>.stride else {
fatalError("BOOM!")
}
var w:Whizzo?
d.withUnsafeBytes({(bytes: UnsafePointer<Whizzo>)->Void in
w = UnsafePointer<Whizzo>(bytes).pointee
})
return w!
}
}
let thing = Whizzo(name:"Bob", num:77, kind:.bzzz)
print("thing = \(thing)")
let dataThing = Whizzo.archive(w: thing)
let convertedThing = Whizzo.unarchive(d: dataThing)
print("convertedThing = \(convertedThing)")
Notes
I couldn't make archive and unarchive instance methods because Data.init(bytes:​count:​) is mutating on the bytes parameter? And self isn't mutable, so... This made no sense to me.
The WhizzoKind enum is in there because that is something I care about. It's not important for the example. Someone might be paranoid about enums like I am.
I had to cobble this answer together from 4 other SO question/answers:
Getting data out of NSData with Swift
Extract struct from NSData in Swift
'bytes' is unavailable: use withUnsafeBytes instead
Unsafe bytes in Swift 3
And these docs:
- http://swiftdoc.org/v3.1/type/UnsafePointer/
And meditating on the Swift closure syntax until I wanted to scream.
So thanks to those other SO askers/authors.
Update
So this will not work across devices. For example, sending from iPhone 7 to Apple Watch. Because the stride is different. The above example is 80 bytes on iPhone 7 Simulator but 40 bytes on Apple Watch Series 2 Simulator.
It looks like the approach (but not syntax) by #niklassaers is still the only one that will work. I'm going to leave this answer here because it might help others with all the new Swift 3 syntax and API changes surrounding this topic.
Our only real hope is this Swift proposal: https://github.com/apple/swift-evolution/blob/master/proposals/0166-swift-archival-serialization.md
The easiest way for basic struct objects is PropertyListEncoder & PropertyListDecoder.
This is the sample code;
Swift 5
struct Packet: Codable {
var name: String
var index: Int
var numberOfPackets: Int
var data: Data
}
func getDataFromPacket(packet: Packet) -> Data?{
do{
let data = try PropertyListEncoder.init().encode(packet)
return data
}catch let error as NSError{
print(error.localizedDescription)
}
return nil
}
func getPacketFromData(data: Data) -> Packet?{
do{
let packet = try PropertyListDecoder.init().decode(Packet.self, from: data)
return packet
}catch let error as NSError{
print(error.localizedDescription)
}
return nil
}
I used Jeff's example to create the following struct:
struct Series {
var name: String?
var season: String?
var episode: String?
init(name: String?, season: String?, episode: String?) {
self.name = name
self.season = season
self.episode = episode
}
static func archive(w: Series) -> Data {
var fw = w
return Data(bytes: &fw, count: MemoryLayout<Series>.stride)
}
static func unarchive(d: Data) -> Series {
guard d.count == MemoryLayout<Series>.stride else {
fatalError("Error!")
}
var w: Series?
d.withUnsafeBytes({(bytes: UnsafePointer<Series>) -> Void in
w = UnsafePointer<Series>(bytes).pointee
})
return w!
}
}
Like Dag mentioned the whole thing is a bit fragile. Sometimes the App crashes when the name contains whitespace or an underline/underscore, and sometimes it crashes just without reason. In all cases the name which is unarchived looks similar to this '4\200a\256'. Surprisingly this is not a problem in the case of season or episode (like in "Season 2"). Here the whitespace doesn't force the app to crash.
Maybe it's an alternative to encode the strings to utf8 but I'm not familiar enough with the archive/unarchive methods to adapt them for this case.
It seems this came out recently, and to me it is looking solid. Didn't try it yet...
https://github.com/a2/MessagePack.swift
Well, Swift doesn't have any magical serialization method, if that's what you are after. Since the good days of C, when you have a struct with a pointer, that is a flag that you can't serialize the bytes of that struct's instance without following the pointers and fetching their data. Same applies to Swift.
Depending on your Serialization needs and constraints, I'd say using NSCoding or even JSON strings will tidy your code and make it more predictable than the current state. Sure, you'll need to write a mapper, and there is an overhead. Everyone will tell you this: "Measure first".
Now, here is the interesting part:
If you really want to inline your data in that struct, and stream the contents without building the packet around NSData as you're doing, you can reserve bytes using Swift Tuples, which work much like how you would reserve bytes in C using char[CONST]:
struct what {
var x = 3
}
sizeof(what)
$R0: Int = 8
struct the {
var y = (3, 4, 5, 7, 8, 9, 33)
}
sizeof(the)
$R1: Int = 56
To expand a bit on this, I think it's pretty horrible, but possible. You can write to the tuple's memory location and read from it using something like this.

Swift - converting from UnsafePointer<UInt8> with length to String

I considered a lot of similar questions, but still can't get the compiler to accept this.
Socket Mobile API (in Objective-C) passes ISktScanDecodedData into a delegate method in Swift (the data may be binary, which I suppose is why it's not provided as string):
func onDecodedData(device: DeviceInfo?, DecodedData d: ISktScanDecodedData?) {
let symbology: String = d!.Name()
let rawData: UnsafePointer<UInt8> = d!.getData()
let rawDataSize: UInt32 = decoded!.getDataSize()
// want a String (UTF8 is OK) or Swifty byte array...
}
In C#, this code converts the raw data into a string:
string s = Marshal.PtrToStringAuto(d.GetData(), d.GetDataSize());
In Swift, I can get as far as UnsafeArray, but then I'm stuck:
let rawArray = UnsafeArray<UInt8>(start: rawData, length: Int(rawDataSize))
Alternatively I see String.fromCString and NSString.stringWithCharacters, but neither will accept the types of arguments at hand. If I could convert from UnsafePointer<UInt8> to UnsafePointer<()>, for example, then this would be available (though I'm not sure if it would even be safe):
NSData(bytesNoCopy: UnsafePointer<()>, length: Int, freeWhenDone: Bool)
Is there an obvious way to get a string out of all this?
This should work:
let data = NSData(bytes: rawData, length: Int(rawDataSize))
let str = String(data: data, encoding: NSUTF8StringEncoding)
Update for Swift 3:
let data = Data(bytes: rawData, count: Int(rawDataSize))
let str = String(data: data, encoding: String.Encoding.utf8)
The resulting string is nil if the data does not represent
a valid UTF-8 sequence.
How about this, 'pure' Swift 2.2 instead of using NSData:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == .None { // no length given, use \0 standard variant
return String.fromCString(cs)
}
let buflen = length + 1
var buf = UnsafeMutablePointer<CChar>.alloc(buflen)
memcpy(buf, cs, length))
buf[length] = 0 // zero terminate
let s = String.fromCString(buf)
buf.dealloc(buflen)
return s
}
}
and Swift 3:
public extension String {
static func fromCString
(cs: UnsafePointer<CChar>, length: Int!) -> String?
{
if length == nil { // no length given, use \0 standard variant
return String(cString: cs)
}
let buflen = length + 1
let buf = UnsafeMutablePointer<CChar>.allocate(capacity: buflen)
memcpy(buf, cs, length)
buf[length] = 0 // zero terminate
let s = String(cString: buf)
buf.deallocate(capacity: buflen)
return s
}
}
Admittedly it's a bit stupid to alloc a buffer and copy the data just to add the zero terminator.
Obviously, as mentioned by Zaph, you need to make sure your assumptions about the string encoding are going to be right.

Pointers, Pointer Arithmetic, and Raw Data in Swift

My application uses a somewhat complex inmutable data structure that is encoded in a binary file. I need to have access to it at the byte level, avoiding any copying. Normally, I would use C or C++ pointer arithmetic and typecasts, to access and interpret the raw byte values. I would like to do the same with Swift.
I have found that the following works:
class RawData {
var data: NSData!
init(rawData: NSData) {
data = rawData
}
func read<T>(byteLocation: Int) -> T {
let bytes = data.subdataWithRange(NSMakeRange(byteLocation, sizeof(T))).bytes
return UnsafePointer<T>(bytes).memory
}
func example_ReadAnIntAtByteLocation5() -> Int {
return read(5) as Int
}
}
However, I am not sure how efficient it is. Do data.subdataWithRange and NSMakeRange allocate objects every time I call them, or are they just syntactic sugar for dealing with pointers?
Is there a better way to do this in Swift?
EDIT:
I have created a small Objective-C class that just encapsulates a function to offset a pointer by a given number of bytes:
#implementation RawDataOffsetPointer
inline void* offsetPointer(void* ptr, int bytes){
return (char*)ptr + bytes;
}
#end
If I include this class in the bridging header, then I can change my read method to
func read<T>(byteLocation: Int) -> T {
let ptr = offsetPointer(data.bytes, CInt(byteLocation))
return UnsafePointer<T>(ptr).memory
}
which will not copy data from my buffer, or allocate other objects.
However, it would still be nice to do some pointer arithmetic from Swift, if it were possible.
If you just want to do it directly, UnsafePointer<T> can be manipulated arithmetically:
let oldPointer = UnsafePointer<()>
let newPointer = oldPointer + 10
You can also cast a pointer like so (UnsafePointer<()> is equivalent to void *)
let castPointer = UnsafePointer<MyStruct>(oldPointer)
I would recommend looking into NSInputStream, which allows you to read NSData as a series of bytes (UInt8 in Swift).
Here is a little sample I put together in the playground:
func generateRandomData(count:Int) -> NSData
{
var array = Array<UInt8>(count: count, repeatedValue: 0)
arc4random_buf(&array, UInt(count))
return NSData(bytes: array, length: count)
}
let randomData = generateRandomData(256 * 1024)
let stream = NSInputStream(data: randomData)
stream.open() // IMPORTANT
var readBuffer = Array<UInt8>(count: 16 * 1024, repeatedValue: 0)
var totalBytesRead = 0
while (totalBytesRead < randomData.length)
{
let numberOfBytesRead = stream.read(&readBuffer, maxLength: readBuffer.count)
// Do something with the data
totalBytesRead += numberOfBytesRead
}
You can create an extension to read primitive types like so:
extension NSInputStream
{
func readInt32() -> Int
{
var readBuffer = Array<UInt8>(count:sizeof(Int32), repeatedValue: 0)
var numberOfBytesRead = self.read(&readBuffer, maxLength: readBuffer.count)
return Int(readBuffer[0]) << 24 |
Int(readBuffer[1]) << 16 |
Int(readBuffer[2]) << 8 |
Int(readBuffer[3])
}
}
I would recommend the simple way to use UnsafeArray.
let data = NSData(contentsOfFile: filename)
let ptr = UnsafePointer<UInt8>(data.bytes)
let bytes = UnsafeBufferPointer<UInt8>(start:ptr, count:data.length)