Last active
August 29, 2015 14:26
-
-
Save rnapier/969cee3cafcc4d1e4a47 to your computer and use it in GitHub Desktop.
Associated types and type erasure for crypto digests http://stackoverflow.com/a/31748616/97337
This file contains hidden or 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
// Just showing how to hide the underlying functions if you wanted to. | |
// Also makes some pieces a little less noisy | |
public struct DigestFunctions<Context> { | |
private var digester: (UnsafePointer<Void>, CC_LONG, UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8> | |
private var updater: (UnsafeMutablePointer<Context>, UnsafePointer<Void>, CC_LONG) -> Int32 | |
private var finalizer: (UnsafeMutablePointer<UInt8>, UnsafeMutablePointer<Context>) -> Int32 | |
} | |
// Digests are reference types because they are stateful. Copying them may lead to confusing results. | |
public protocol Digest: class { | |
typealias Context | |
var context: Context { get set } | |
var length: Int { get } | |
var functions: DigestFunctions<Context> { get } // functions itself is public, but data in it is private | |
} | |
// Some helpers on all digests to make them act more Swiftly without having to deal with UnsafeMutablePointers. | |
public extension Digest { | |
public func digest(data: [UInt8]) -> [UInt8] { | |
return perform { functions.digester(UnsafePointer<Void>(data), CC_LONG(data.count), $0) } | |
} | |
public func update(data: [UInt8]) { | |
functions.updater(&context, UnsafePointer<Void>(data), CC_LONG(data.count)) | |
} | |
public func final() -> [UInt8] { | |
return perform { functions.finalizer($0, &context) } | |
} | |
// Helper that wraps up "create a buffer, update buffer, return buffer" | |
private func perform(f: (UnsafeMutablePointer<UInt8>) -> ()) -> [UInt8] { | |
var hash = [UInt8](count: length, repeatedValue: 0) | |
f(&hash) | |
return hash | |
} | |
} | |
// Example of creating a new digest | |
public final class SHA1: Digest { | |
public var context = CC_SHA1_CTX() | |
public let length = Int(CC_SHA1_DIGEST_LENGTH) | |
public let functions = DigestFunctions(digester: CC_SHA1, updater: CC_SHA1_Update, finalizer: CC_SHA1_Final) | |
} | |
// Type-eraser, so we can talk about arbitrary digests without worrying about the underlying associated type. | |
// See http://robnapier.net/erasure | |
// So now we can say things like `let digests = [AnyDigest(SHA1()), AnyDigest(SHA256())]` | |
// If this were the normal use-case, you could rename "Digest" as "DigestAlgorithm" and rename "AnyDigest" as "Digest" | |
// for convenience | |
public final class AnyDigest: Digest { | |
public var context: Void = () | |
public let length: Int | |
public let functions: DigestFunctions<Void> | |
public init<D: Digest>(_ digest: D) { | |
length = digest.length | |
functions = DigestFunctions( | |
digester: digest.functions.digester, | |
updater: { digest.functions.updater(&digest.context, $1, $2) }, | |
finalizer: { (hash, _) in digest.functions.finalizer(hash, &digest.context) }) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment