1

I have the following extension to convert UUIDs to base64 url safe strings and vise versa. I'm struggling with my base64UrlSafeString not returning a utf8 string because the underlying data is an array of UInt8. At least I think that's the problem because the last line of decodeBase64UrlSafeString() returns nil. Is there a safe way to convert the 16 bytes from the UUID into a Data object with utf8 encoding?

let uuid = UUID(uuidString: "9823c260-4feb-11e9-990c-354fb53401ce")!
print(uuid.base64UrlSafeString) //mCPCYE_rEemZDDVPtTQBzg
print(UUID.decodeBase64UrlSafeString(uuid.base64UrlSafeString)) //nil

The UUID extension:

extension UUID {
    var base64UrlSafeString: String {
        var result = self.data.base64EncodedString()
        result = result.replacingOccurrences(of: "+", with: "-")
        result = result.replacingOccurrences(of: "/", with: "_")
        result = result.replacingOccurrences(of: "=", with: "")
        return result
    }
    var data: Data {
        var result = Data()
        result.append(uuid.0)
        result.append(uuid.1)
        result.append(uuid.2)
        result.append(uuid.3)
        result.append(uuid.4)
        result.append(uuid.5)
        result.append(uuid.6)
        result.append(uuid.7)
        result.append(uuid.8)
        result.append(uuid.9)
        result.append(uuid.10)
        result.append(uuid.11)
        result.append(uuid.12)
        result.append(uuid.13)
        result.append(uuid.14)
        result.append(uuid.15)
        return result
    }
    static func decodeBase64UrlSafeString(_ base_64_uuid: String ) -> String? {
        var base_64_uuid = base_64_uuid
        base_64_uuid = base_64_uuid.replacingOccurrences(of: "-", with: "+")
        base_64_uuid = base_64_uuid.replacingOccurrences(of: "_", with: "/")
        while base_64_uuid.count % 4 != 0 {
            base_64_uuid = base_64_uuid.appending("=")
        }
        guard let data = Data(base64Encoded: base_64_uuid) else { return nil }
        print("Got here") //code makes it here
        return String(data: data, encoding: .utf8) //returns nil!
    }
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
Kacy
  • 3,330
  • 4
  • 29
  • 57
  • I know no Swift, so I can't help with that, but why do you want to encode from `UUID` to a base64 encoded string, but decode back to a `String` object (instead of constructing a `UUID` again)? – Joachim Sauer Oct 25 '21 at 10:52

1 Answers1

3

The UUID data is not a valid utf8 encoded string. What you need is to convert the data to UUID and then return the uuidString:

static func decodeBase64UrlSafeString(_ base_64_uuid: String) -> String? {
    var base_64_uuid = base_64_uuid
        .replacingOccurrences(of: "-", with: "+")
        .replacingOccurrences(of: "_", with: "/")
    while base_64_uuid.count % 4 != 0 { base_64_uuid += "=" }
    guard let data = Data(base64Encoded: base_64_uuid), data.count == 16 else { return nil }
    let uuid = data.withUnsafeBytes { $0.load(as: UUID.self) }
    return uuid.uuidString
}
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • Do you know if there's a difference between your last 2 lines and `return NSUUID( uuidBytes: [UInt8](data) ).uuidString` ? For example, if `base_64_uuid` comes from an external source, would one be able to deal with it not being a UInt8 array of 16 bytes? – Kacy Oct 25 '21 at 11:50
  • You can simply add a guard and check the data size. `guard let data = Data(base64Encoded: base_64_uuid), data.count == 16 else { return nil }` – Leo Dabus Oct 25 '21 at 11:52
  • I accepted the answer, but if anyone is aware of any benefit in using `withUnsafeBytes()` compared to the `NSUUID()` approach I mentioned in my previous comment, please reply. – Kacy Oct 25 '21 at 12:08
  • I don’t know what you mean by benefits. Just use whatever you feel comfortable with. – Leo Dabus Oct 25 '21 at 12:12