how to specify packed alignment in pure swift without C bridging?
There’s no way to do this, but that’s not because of the packed part of your question but rather because Swift has no way to enforce any sort of structure layout. If you want to a structure with a known layout, you must import it from C.
With regards your deliverPacket(_:)
example, be aware that load(as:)
requires that the pointer be aligned, trapping if it’s not. For example, assuming this C structure:
struct TwoUInt8sAndAUInt16 {
unsigned char u8a;
unsigned char u8b;
unsigned short u16;
};
which is laid out how you’d expect:
print(MemoryLayout<TwoUInt8sAndAUInt16>.size) // 4
print(MemoryLayout<TwoUInt8sAndAUInt16>.alignment) // 2
print(MemoryLayout.offset(of: \TwoUInt8sAndAUInt16.u8a)!) // 0
print(MemoryLayout.offset(of: \TwoUInt8sAndAUInt16.u8b)!) // 1
print(MemoryLayout.offset(of: \TwoUInt8sAndAUInt16.u16)!) // 2
this code works:
let d = Data([0x01, 0x02, 0x03, 0x04, 0x05])
d.withUnsafeBytes { buf in
print(buf.baseAddress!.load(as: TwoUInt8sAndAUInt16.self))
}
but this code traps:
d.dropFirst().withUnsafeBytes { buf in
print(buf.baseAddress!.load(as: TwoUInt8sAndAUInt16.self))
}
Coming back to Robert Carl Rice’s original issue, the best way (IMO) to handle this is to avoid writing unsafe pointers but instead write a proper parser. There’s not enough info in their post to show this exactly, so here’s an example inspired by it:
enum Packet {
case hello(ServiceID, String)
case goodbye(ServiceID)
struct ServiceID {
var rawValue: UInt16
}
}
func parsePacket(data: inout Data) -> Packet? {
guard let tag = data.popFirst() else { return nil }
guard
let b1 = data.popFirst(),
let b2 = data.popFirst()
else { return nil }
let serviceID = Packet.ServiceID(rawValue: UInt16(b1) * 256 + UInt16(b2))
switch tag {
case 0x01:
guard let nameCount = data.popFirst() else { return nil }
guard data.count >= nameCount else { return nil }
let nameBytes = data.prefix(Int(nameCount))
data.removeFirst(Int(nameCount))
guard let name = String(bytes: nameBytes, encoding: .utf8) else { return nil }
return Packet.hello(serviceID, name)
case 0x02:
return Packet.goodbye(serviceID)
default: return nil
}
}
While this seems wordy, it’s easy to write some helpers that shrink it down. My favourite example of this is Soroush Khanlou’s Regexes vs Combinatorial Parsing article.
This approach has some key advantages:
-
It does not rely on how the compiler lays out structures, something that’s not allowed for in Swift and is challenging even in C.
-
It handles endianness correctly.
-
It handle things that are tricky to represent in C, like variable-length strings.
-
It will perform better than you expect (-:
-
It’s memory safe.
Do not underestimate the value of that last point. It’s clear that the memory unsafeness inherent to C-based languages is a major cause of security vulnerabilities, and to get out from under that you have to use Swift safely. This is particularly important when, as shown in this example, you’re working with data coming off the network.
Share and Enjoy
Quinn “The Eskimo!” @ DTS @ Apple