Skip to content

Instantly share code, notes, and snippets.

@joshuadutton
Created April 15, 2016 03:03
Show Gist options
  • Save joshuadutton/294a2f1beefeaa5414ca87faec3ef56f to your computer and use it in GitHub Desktop.
Save joshuadutton/294a2f1beefeaa5414ca87faec3ef56f to your computer and use it in GitHub Desktop.
import Foundation
enum NSDataError: ErrorType {
case OutOfBounds(data: NSData)
case InvalidASCIIString(data: NSData)
case ValueDoesNotMatchEnum(value: UInt8, type: Any)
}
// MARK: - NSData CollectionType conformance
extension NSData: CollectionType {
public subscript (position: Int) -> UInt8 {
return UnsafePointer<UInt8>(bytes)[position]
}
public subscript (bounds: Range<Int>) -> NSData {
return subdataWithRange(NSRange(bounds))
}
public var startIndex: Int { return 0 }
public var endIndex: Int { return length }
}
extension NSData {
/// String formated with the hex value for each of the receiver's bytes separated by a colon (e.g. 01:23:45:67:89:ab)
var colonSeparatedRepresentation: String {
let byteStrings = self.map { return String(format: "%02x", $0) }
return byteStrings.joinWithSeparator(":")
}
/// Returns a data object containing the receiver’s bytes that start at location and end at location + length.
/// If location and length are out of bounds, it raises an `NSRangeException`
func subdataAtLocation(location: Int, length: Int) -> NSData {
return subdataWithRange(NSRange(location: location, length: length))
}
/// Returns a data object containing the receiver’s bytes that start at location and end at location + length
/// Throws an error if the given location and length are out of bounds.
func readSubdataAtLocation(location: Int, length: Int) throws -> NSData {
if self.length < location + length {
throw NSDataError.OutOfBounds(data: self)
}
return subdataWithRange(NSRange(location: location, length: length))
}
/// Returns a data object containing the receiver’s bytes that are in the range
/// Throws an error if the given range is out of bounds.
func readSubdataInRange(range: Range<Int>) throws -> NSData {
if range.endIndex > self.length {
throw NSDataError.OutOfBounds(data: self)
}
return self[range]
}
/// Returns an instance of T read from the reciever at the given location.
/// Example: if T == Int32, this will read 4 bytes starting at index and return an Int32
/// Warning: the length of Int is hardware specific.
/// Throws an error if the location + the length of type T is out of bounds.
func readAtLocation<T>(location: Int) throws -> T {
return try readAtLocation(location, length: sizeof(T))
}
func readAtLocation<T>(location: Int, length: Int) throws -> T {
let data = try readSubdataAtLocation(location, length: length)
return UnsafePointer<T>(data.bytes).memory
}
/// Returns an instance of T read from the reciever at the given location
/// when T is a RawRepresentable type and T.RawValue is a byte (UInt8).
/// Throws an error if the location is out of bounds.
func readAtLocation<T: RawRepresentable where T.RawValue == UInt8>(location: Int) throws -> T {
let rawValue = try readByteAtLocation(location)
guard let anEnum = T(rawValue: rawValue) else {
throw NSDataError.ValueDoesNotMatchEnum(value: rawValue, type: T.self)
}
return anEnum
}
/// Returns the byte at the given location.
/// Throws an error if the location is out of bounds.
func readByteAtLocation(location: Int) throws -> UInt8 {
return try readAtLocation(location)
}
/// Returns an ASCII formatted string that starts at location and ends at location + length
/// Throws an error if the given location and length are out of bounds.
func readASCIIStringAtLocation(location: Int, length: Int) throws -> String {
let stringData = try self.readSubdataAtLocation(0, length: length)
guard let string = String(data: stringData, encoding: NSASCIIStringEncoding) else {
throw NSDataError.InvalidASCIIString(data: self)
}
return string
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment