Pointers, Pointer Arithmetic, and Raw Data in Swift - 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)

Related

Copying swift string to fixed size char[][]

I have a C struct like this.
struct someStruct {
char path[10][MAXPATHLEN];
};
I'd like to copy a list of Swift strings into the char[10][] array.
For me it's very challenging to handle c two-dimensional char array in Swift. Could anyone share some code which can work with Swift 5? Thanks!
C Arrays are imported to Swift as tuples. Here we have a two-dimensional C array, which becomes a nested tuple in Swift:
public struct someStruct {
public var path: (
(Int8, ..., Int8),
(Int8, ..., Int8),
...
(Int8, ..., Int8)
)
}
There is no really “nice” solution that I am aware of, but using the fact that Swift preserves the memory layout of imported C structures (source), one can achive the goal with some pointer magic:
var s = someStruct()
let totalSize = MemoryLayout.size(ofValue: s.path)
let itemSize = MemoryLayout.size(ofValue: s.path.0)
let numItems = totalSize / itemSize
withUnsafeMutablePointer(to: &s.path) {
$0.withMemoryRebound(to: Int8.self, capacity: totalSize) { ptr in
for i in 0..<numItems {
let itemPtr = ptr + i * itemSize
strlcpy(itemPtr, "String \(i)", itemSize)
}
print(ptr)
}
}
ptr is a pointer to s.path, and itemPtr is pointer to s.path[i]. strlcpy copies the string, here we use the fact that one can pass a Swift string directly to a C function taking a const char* argument (and a temporary null-terminated UTF-8 representation is created automatically).
I strongly encourage you to use some kind of helper methods.
Example:
/* writes str to someStruct instance at index */
void writePathToStruct(struct someStruct* s, size_t index, const char* str) {
assert(index < 10 && "Specified index is out of bounds");
strcpy(s->path[index], str);
}
Now, when calling this function, filling the array looks much cleaner:
var someStructInstance = someStruct()
let pathIndex: Int = 3
let path = "/dev/sda1"
let encoding = String.Encoding.ascii
withUnsafeMutablePointer(to: &someStructInstance) { pointer -> Void in
writePathToStruct(pointer, pathIndex, path.cString(using: encoding)!)
}
By design, tuples can not be accessed by variable index. Reading statically can thus be done without a helper function.
let pathRead = withUnsafeBytes(of: &someStructInstance.path.3) { pointer -> String? in
return String(cString: pointer.baseAddress!.assumingMemoryBound(to: CChar.self), encoding: encoding)
}
print(pathRead ?? "<Empty path>")
However, I assume you will definitely have to read the array with a dynamic index.
In that case, I encourage you to use a helper method as well:
const char* readPathFromStruct(const struct someStruct* s, size_t index) {
assert(index < 10 && "Specified index is out of bounds");
return s->path[index];
}
which will result in a much cleaner Swift code:
pathRead = withUnsafePointer(to: &someStructInstance) { pointer -> String? in
return String(cString: readPathFromStruct(pointer, 3), encoding: encoding)
}

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 NSData getBytes reversed

I made an app communicating with a device with Bluetooth Low Energy.
Basically, my app and this device have their own message syntax. They exchange data as bytes and each values in thoses data are reversed.
My problem is that after reversing back value, when I'm converting a 3 bytes value to an Int32, the NSData.getBytes function seems to reverse the value, so I have a wrong value. Example:
var value; // containing [ 0x01, 0xD3, 0x00 ]
value = value.reverse(); // Reverse back : [ 0x00, 0xD3, 0x01 ]
let numb = value.getUInt32(); // Numb will be 119552, instead of 54017...
I don't know if I'm clear enough on my problem, but here is my code. A function which reverse back data and then tries to convert data to int.
// Those functions are in an extension of NSData
func getRUInt32(range:NSRange) -> UInt32
{
var data = message.subdataWithRange(range); // Extract data from main message
data = data.reverse(); // Reverse back data
return data.getUInt32(); // Convert to UInt32
}
func getUInt32() -> UInt32
{
var value:Int32 = 0;
getBytes(&value, length: self.length);
return UInt32(value);
}
func reverse() -> NSData
{
let count:Int = length / sizeof(UInt8);
var array = [UInt8](count: count, repeatedValue: 0);
getBytes(&array, length: count * sizeof(UInt8));
var reversedArray = [UInt8](count: count, repeatedValue: 0);
for index in 0..<array.count
{
reversedArray[index] = array[array.count - index - 1];
}
return NSData(bytes: reversedArray, length: reversedArray.count);
}
You should have a look at the byte order utilities reference:
https://developer.apple.com/library/ios/documentation/CoreFoundation/Reference/CFByteOrderUtils/index.html#//apple_ref/c/func/CFConvertDoubleHostToSwapped
You identify the native format of the current platform using the
CFByteOrderGetCurrent function. Use functions such as
CFSwapInt32BigToHost and CFConvertFloat32HostToSwapped to convert
values between different byte order formats.

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.