Skip to content

Instantly share code, notes, and snippets.

@mikeash
Created September 19, 2014 08:30
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mikeash/a4968f890595302cb9e2 to your computer and use it in GitHub Desktop.
Save mikeash/a4968f890595302cb9e2 to your computer and use it in GitHub Desktop.
import Darwin
// Swift hates uninitialized values but we need to create stuff without
// an explicit initial value. This protocol is for anything that can be
// created with any sort of default value (e.g. all integer types init
// with zero.
protocol Initable {
init()
}
// Byte-level (unaligned) storage formed by concatenating two other
// storage types together
struct ByteStorageAdd<T: Initable, U: Initable>: Initable {
var t = T()
var u = U()
}
// The underlying type for byte-level (unaligned) storage is Int8.
// It needs to be Initable so we can use it sanely. The builtin
// types already define init(), we just need to declare conformance.
extension Int8: Initable {}
// Build up various byte-level storage sizes starting from a single
// byte. Other sizes can be constructed using ByteStorageAdd and
// can be added as typealiases here if that's useful
class ByteStorage {
typealias One = Int8
typealias Two = ByteStorageAdd<One, One>
typealias Four = ByteStorageAdd<Two, Two>
typealias Eight = ByteStorageAdd<Four, Four>
}
// We need the equivalent of sizeof(T) at the level of the type system.
// The Storeable protocol handles this by mapping a Storeable type to
// a storage type which should be one of the ByteStorage typealiases or
// a ByteStorageAdd
protocol Storeable: Initable {
typealias Storage: Initable
}
// Fill out Storeable for all the explicitly-sized integer types
extension Int8: Storeable {
typealias Storage = ByteStorage.One
}
extension Int16: Storeable {
typealias Storage = ByteStorage.Two
}
extension Int32: Storeable {
typealias Storage = ByteStorage.Four
}
extension Int64: Storeable {
typealias Storage = ByteStorage.Eight
}
extension UInt8: Storeable {
typealias Storage = ByteStorage.One
}
extension UInt16: Storeable {
typealias Storage = ByteStorage.Two
}
extension UInt32: Storeable {
typealias Storage = ByteStorage.Four
}
extension UInt64: Storeable {
typealias Storage = ByteStorage.Eight
}
// Perform a byte copy from one type to another, swapping the bytes
// as it goes. This is basically an endian-swapping version of
// unsafeBitCast.
func SwappedRead<From, To: Initable>(var from: From) -> To {
precondition(sizeof(To) == sizeof(From))
let size = sizeof(To)
var to = To()
withUnsafePointer(&from) {
(fromPtr: UnsafePointer<From>) -> Void in
withUnsafeMutablePointer(&to) {
(toPtr: UnsafeMutablePointer<To>) in
let fromBytePtr = UnsafePointer<Int8>(fromPtr)
let toBytePtr = UnsafeMutablePointer<Int8>(toPtr)
for i in 0..<size {
toBytePtr[i] = fromBytePtr[size - i - 1]
}
}
}
return to
}
// We need a concept of endianness in the type system. Fields can't contain
// any storage beyond their literal underlying storage type, so they can't
// encode their endianness as a property. By encoding endianness within the
// type system, we can define fields of different endiannesses at the type
// level without needing any additional storage in memory at runtime.
protocol Endianness {
class func read<From, To: Initable>(from: From) -> To
}
// Define big and little endian types, using either SwappedRead or unsafeBitCast.
// TODO: use conditional compilation to switch the implementations on big-endian
// systems.
class BigEndian: Endianness {
class func read<From, To: Initable>(from: From) -> To {
return SwappedRead(from)
}
}
class LittleEndian: Endianness {
class func read<From, To: Initable>(from: From) -> To {
return unsafeBitCast(from, To.self)
}
}
// An integer field that maps directly to underlying storage and reads/writes
// the values on demand.
struct IntField<T: Storeable, E: Endianness> {
var storage = T.Storage()
var value: T {
get {
return E.read(storage)
}
set {
storage = E.read(newValue)
}
}
}
// Take an arbitrary value and extract its contents as an array of Int8.
// The type should, obviously, be built to handle this propertly.
func ToByteArray<T>(var value: T) -> [Int8] {
return withUnsafePointer(&value) {
(ptr: UnsafePointer<T>) -> [Int8] in
let buffer = UnsafeBufferPointer<Int8>(start: UnsafePointer<Int8>(ptr), count: sizeof(T))
return [Int8](buffer)
}
}
// Take an array of Int8 and splat those bytes into an arbitrary type.
// The array's length must equal the size of the type. Again, the type
// should be built to handle this.
func FromByteArray<T>(array: [Int8]) -> T {
precondition(array.count == sizeof(T))
return array.withUnsafeBufferPointer {
return UnsafePointer<T>($0.baseAddress).memory
}
}
// An example struct with a bunch of integer fields.
struct TestStruct {
var tag = IntField<UInt8, BigEndian>()
var value = IntField<Int32, BigEndian>()
var value2 = IntField<Int32, LittleEndian>()
var value3 = IntField<UInt16, BigEndian>()
var value4 = IntField<UInt64, BigEndian>()
var value5 = IntField<UInt64, LittleEndian>()
}
var x = TestStruct()
x.tag.value = 42
x.value.value = 1
x.value2.value = 2
x.value3.value = 3
x.value4.value = 4
x.value5.value = 5
var data = ToByteArray(x)
println(data)
// [42, 0, 0, 0, 1, 2, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 4, 5, 0, 0, 0, 0, 0, 0, 0]
var y: TestStruct = FromByteArray(data)
println((y.tag.value, y.value.value, y.value2.value, y.value3.value, y.value4.value, y.value5.value))
// (42, 1, 2, 3, 4, 5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment