Put simply I am trying to convert a #define
macro into a native Swift data structure of some sort. Just not sure how or what kind.
I would like to try and replicate the following #define
from Objective-C to Swift. Source: JoeKun/FileMD5Hash
#define FileHashComputationContextInitialize(context, hashAlgorithmName) \
CC_##hashAlgorithmName##_CTX hashObjectFor##hashAlgorithmName; \
context.initFunction = (FileHashInitFunction)&CC_##hashAlgorithmName##_Init; \
context.updateFunction = (FileHashUpdateFunction)&CC_##hashAlgorithmName##_Update; \
context.finalFunction = (FileHashFinalFunction)&CC_##hashAlgorithmName##_Final; \
context.digestLength = CC_##hashAlgorithmName##_DIGEST_LENGTH; \
context.hashObjectPointer = (uint8_t **)&hashObjectFor##hashAlgorithmName
Obviously #define
does not exist in Swift; therefore I'm not looking for a 1:1 port. More generally just the spirit of it.
To start, I made an enum
called CryptoAlgorithm
. I only care to support two crypto algorithms for the sake of this question; but there should be nothing stopping me from extending it further.
enum CryptoAlgorithm {
case MD5, SHA1
}
So far so good. Now to implement the digestLength
.
enum CryptoAlgorithm {
case MD5, SHA1
var digestLength: Int {
switch self {
case .MD5:
return Int(CC_MD5_DIGEST_LENGTH)
case .SHA1:
return Int(CC_SHA1_DIGEST_LENGTH)
}
}
Again, so far so good. Now to implement the initFunction
.
enum CryptoAlgorithm {
case MD5, SHA1
var digestLength: Int {
switch self {
case .MD5:
return Int(CC_MD5_DIGEST_LENGTH)
case .SHA1:
return Int(CC_SHA1_DIGEST_LENGTH)
}
var initFunction: UnsafeMutablePointer<CC_MD5_CTX> -> Int32 {
switch self {
case .MD5:
return CC_MD5_Init
case .SHA1:
return CC_SHA1_Init
}
}
}
Crash and burn. 'CC_MD5_CTX' is not identical to 'CC_SHA1_CTX'
. The problem is that CC_SHA1_Init
is a UnsafeMutablePointer<CC_SHA1_CTX> -> Int32
. Therefore, the two return types are not the same.
Is an enum
the wrong approach? Should I be using generics? If so, how should the generic be made? Should I provide a protocol that both CC_MD5_CTX
and CC_SHA1_CTX
and then are extended by and return that?
All suggestions are welcome (except to use an Objc bridge).
I don't know if I love where this is going in the original ObjC code, because it's pretty type-unsafe. In Swift you just need to make all the type unsafety more explicit:
var initFunction: UnsafeMutablePointer<Void> -> Int32 {
switch self {
case .MD5:
return { CC_MD5_Init(UnsafeMutablePointer<CC_MD5_CTX>($0)) }
case .SHA1:
return { CC_SHA1_Init(UnsafeMutablePointer<CC_SHA1_CTX>($0)) }
}
}
The more "Swift" way of approaching this would be with protocols, such as:
protocol CryptoAlgorithm {
typealias Context
init(_ ctx: UnsafeMutablePointer<Context>)
var digestLength: Int { get }
}
Then you'd have something like (untested):
struct SHA1: CryptoAlgorithm {
typealias Context = CC_SHA1_CONTEXT
private let context: UnsafeMutablePointer<Context>
init(_ ctx: UnsafeMutablePointer<Context>) {
CC_SHA1_Init(ctx) // This can't actually fail
self.context = ctx // This is pretty dangerous.... but matches above. (See below)
}
let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}
But I'd be strongly tempted to hide the context, and just make it:
protocol CryptoAlgorithm {
init()
var digestLength: Int { get }
}
struct SHA1: CryptoAlgorithm {
private var context = CC_SHA1_CTX()
init() {
CC_SHA1_Init(&context) // This is very likely redundant.
}
let digestLength = Int(CC_SHA1_DIGEST_LENGTH)
}
Why do you need to expose the fact that it's CommonCrypto under the covers? And why would you want to rely on the caller to hold onto the context for you? If it goes out of scope, then later calls will crash. I'd hold onto the context inside.
Getting more closely to your original question, consider this (compiles, but not tested):
// Digests are reference types because they are stateful. Copying them may lead to confusing results.
protocol Digest: class {
typealias Context
var context: Context { get set }
var length: Int { get }
var digester: (UnsafePointer<Void>, CC_LONG, UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8> { get }
var updater: (UnsafeMutablePointer<Context>, UnsafePointer<Void>, CC_LONG) -> Int32 { get }
var finalizer: (UnsafeMutablePointer<UInt8>, UnsafeMutablePointer<Context>) -> Int32 { get }
}
// Some helpers on all digests to make them act more Swiftly without having to deal with UnsafeMutablePointers.
extension Digest {
func digest(data: [UInt8]) -> [UInt8] {
return perform { digester(UnsafePointer<Void>(data), CC_LONG(data.count), $0) }
}
func update(data: [UInt8]) {
updater(&context, UnsafePointer<Void>(data), CC_LONG(data.count))
}
func final() -> [UInt8] {
return perform { 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
final class SHA1: Digest {
var context = CC_SHA1_CTX()
let length = Int(CC_SHA1_DIGEST_LENGTH)
let digester = CC_SHA1
let updater = CC_SHA1_Update
let finalizer = CC_SHA1_Final
}
// And here's what you change to make another one
final class SHA256: Digest {
var context = CC_SHA256_CTX()
let length = Int(CC_SHA256_DIGEST_LENGTH)
let digester = CC_SHA256
let updater = CC_SHA256_Update
let finalizer = CC_SHA256_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
final class AnyDigest: Digest {
var context: Void = ()
let length: Int
let digester: (UnsafePointer<Void>, CC_LONG, UnsafeMutablePointer<UInt8>) -> UnsafeMutablePointer<UInt8>
let updater: (UnsafeMutablePointer<Void>, UnsafePointer<Void>, CC_LONG) -> Int32
let finalizer: (UnsafeMutablePointer<UInt8>, UnsafeMutablePointer<Void>) -> Int32
init<D: Digest>(_ digest: D) {
length = digest.length
digester = digest.digester
updater = { digest.updater(&digest.context, $1, $2) }
finalizer = { (hash, _) in digest.finalizer(hash, &digest.context) }
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With