Skip to content

Instantly share code, notes, and snippets.

@russbishop
Last active May 17, 2016 17:49
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save russbishop/1510acbb39ff90006efb67c609f5b27b to your computer and use it in GitHub Desktop.
Save russbishop/1510acbb39ff90006efb67c609f5b27b to your computer and use it in GitHub Desktop.
PointEncoder from russbishop.net/packing-bytes-in-swift
// 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