Last active
May 17, 2016 17:49
-
-
Save russbishop/1510acbb39ff90006efb67c609f5b27b to your computer and use it in GitHub Desktop.
PointEncoder from russbishop.net/packing-bytes-in-swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Written by Russ Bishop | |
// MIT licensed, use freely. | |
// No warranty, not suitable for any purpose. Use at your own risk! | |
struct PointEncoder { | |
// When parsing if we get a wildly large value we can | |
// assume denial of service or corrupt data. | |
static let MaxPoints = 1_310_719 | |
// How big an Int64 is | |
private static let _sizeOfCount = sizeof(Int64.self) | |
// How big a point (two Float32s are) | |
private static let _sizeOfPair = 2 * sizeof(Float32.self) | |
static func encodePoints(points: [CGPoint]) -> NSData? { | |
guard !points.isEmpty && points.count < MaxPoints else { return nil } | |
// Total size of the buffer | |
let bufferLength = _sizeOfCount + (points.count * _sizeOfPair) | |
precondition(bufferLength >= (_sizeOfCount + _sizeOfPair), "Empty buffer?") | |
precondition(bufferLength < megabytes(10), "Buffer would exceed 10MB") | |
let rawMemory = UnsafeMutablePointer<Void>.alloc(bufferLength) | |
// Failed to allocate memory | |
guard rawMemory != nil else { return nil } | |
// Store the point count in the first portion of the buffer | |
UnsafeMutablePointer<Int64>(rawMemory).memory = Int64(points.count) | |
// The remaining bytes are for the Float32 pairs | |
let buffer = UnsafeMutablePointer<Float32>(rawMemory + _sizeOfCount) | |
// Store the points | |
for (index, point) in points.enumerate() { | |
// Since buffer is UnsafeMutablePointer<Float32>, addition counts | |
// the number of Float32s, *not* the number of bytes! | |
let ptr = buffer + (index * 2) | |
// Store the point values. | |
ptr.memory = Float32(point.x) | |
ptr.advancedBy(1).memory = Float32(point.y) | |
} | |
// We can tell NSData not to bother copying memory. | |
// For consistency and since we can't guarantee the memory allocated | |
// by UnsafeMutablePointer can just be freed, we provide a deallocator | |
// block. | |
return NSData( | |
bytesNoCopy: rawMemory, | |
length: bufferLength, | |
deallocator: { (ptr, length) in | |
// If ptr held more complex types, failing to call | |
// destroy will cause lots of leakage. | |
// No one wants leakage. | |
ptr.destroy(length) | |
ptr.dealloc(length) | |
}) | |
} | |
static func decodePoints(data: NSData) -> [CGPoint] { | |
// If we don't have at least one point pair | |
// and a size byte, bail. | |
guard | |
data.bytes != nil && | |
data.length > (_sizeOfCount + _sizeOfPair) | |
else { return [] } | |
let rawMemory = data.bytes | |
let buffer = rawMemory + _sizeOfCount | |
// Extract the point count as an Int64 | |
let pointCount64 = UnsafePointer<Int64>(rawMemory).memory | |
// Swift is safer than C here; you can't | |
// accidentally overflow/underflow and not | |
// trigger a trap, but I am still checking | |
// to provide better error messages. | |
// In all cases, better to kill the process | |
// than corrupt memory. | |
precondition( | |
Int64(MaxPoints) < Int64(Int32.max), | |
"MaxPoints would overflow on 32-bit platforms") | |
precondition( | |
pointCount64 > 0 && pointCount64 < Int64(MaxPoints), | |
"Invalid pointCount = \(pointCount64)") | |
// On 32-bit systems this would trap if | |
// MaxPoints were too big and we didn't | |
// check above. | |
let pointCount = Int(pointCount64) | |
precondition( | |
_sizeOfPair + (_sizeOfCount * pointCount) <= data.length, | |
"Size lied or buffer truncated") | |
var points: [CGPoint] = [] | |
// Small optimization since | |
// we know the array size | |
points.reserveCapacity(pointCount) | |
for ptr in (0..<pointCount).map({ | |
// buffer points past the size header | |
// Again, since the pointer knows we are | |
// counting Float32 values we want the | |
// number of Float32s, *not* their size | |
// in bytes! | |
UnsafePointer<Float32>(buffer) + (2 * $0) | |
}) { | |
points.append( | |
CGPoint( | |
x: CGFloat(ptr.memory), | |
y: CGFloat(ptr.advancedBy(1).memory)) | |
) | |
} | |
return points | |
} | |
} | |
func kilobytes(value: Int) -> Int { | |
return value * 1024 | |
} | |
func megabytes(value: Int) -> Int { | |
return kilobytes(value * 1024) | |
} | |
func gigabytes(value: Int) -> Int { | |
return megabytes(value * 1024) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment