ULID.swift/Sources/ULID/Data+Base32.swift

212 lines
7.5 KiB
Swift

//
// Data+Base32.swift
// ULID
//
// Created by Yasuhiro Hatta on 2019/01/11.
// Copyright © 2019 yaslab. All rights reserved.
//
import Foundation
enum Base32 {
static let crockfordsEncodingTable: [UInt8] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".utf8.map({ $0 })
static let crockfordsDecodingTable: [UInt8] = [
// 0 1 2 3 4 5 6 7 8 9 a b c d e f
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 0
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 1
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 2
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 3
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, // 4
0x16, 0x17, 0x18, 0x19, 0x1a, 0xff, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, // 5
0xff, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x01, 0x12, 0x13, 0x01, 0x14, 0x15, 0x00, // 6
0x16, 0x17, 0x18, 0x19, 0x1a, 0xff, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0xff, 0xff, 0xff, 0xff, 0xff, // 7
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 8
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // 9
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // a
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // b
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // c
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // d
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // e
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff // f
]
}
extension Data {
/// Decode Crockford's Base32
init?(base32Encoded base32String: String, using table: [UInt8] = Base32.crockfordsDecodingTable) {
var base32String = base32String
while let last = base32String.last, last == "=" {
base32String.removeLast()
}
let result: Data? = base32String.withCString(encodedAs: Unicode.UTF8.self) { (src) in
func _strlen(_ str: UnsafePointer<UInt8>) -> Int {
var str = str
var i = 0
while str.pointee != 0 {
str += 1
i += 1
}
return i
}
let srclen = _strlen(src)
guard [0, 2, 4, 5, 7].contains(srclen % 8) else {
return nil
}
let dstlen = srclen * 5 / 8
var buffer = Data(count: dstlen)
let success: Bool = buffer.withUnsafeMutableBytes { (dst: UnsafeMutablePointer<UInt8>) in
var srcleft = srclen
var srcp = src
var dstp = dst
let work = UnsafeMutablePointer<UInt8>.allocate(capacity: 8)
defer { work.deallocate() }
while srcleft > 0 {
let worklen = Swift.min(8, srcleft)
for i in 0 ..< worklen {
work[i] = table[Int(srcp[i])]
if work[i] == 0xff {
return false
}
}
switch worklen {
case 8:
dstp[4] = (work[6] << 5) | (work[7] )
fallthrough
case 7:
dstp[3] = (work[4] << 7) | (work[5] << 2) | (work[6] >> 3)
fallthrough
case 5:
dstp[2] = (work[3] << 4) | (work[4] >> 1)
fallthrough
case 4:
dstp[1] = (work[1] << 6) | (work[2] << 1) | (work[3] >> 4)
fallthrough
case 2:
dstp[0] = (work[0] << 3) | (work[1] >> 2)
default:
break
}
srcp += 8
srcleft -= 8
dstp += 5
}
return true
}
guard success else {
return nil
}
return buffer
}
guard let data = result else {
return nil
}
self = data
}
/// Encode Crockford's Base32
func base32EncodedString(padding: Bool = true, using table: [UInt8] = Base32.crockfordsEncodingTable) -> String {
var srcleft = self.count
let dstlen: Int
if padding {
dstlen = (self.count + 4) / 5 * 8
} else {
dstlen = (self.count * 8 + 4) / 5
}
var dstleft = dstlen
return self.withUnsafeBytes { (src: UnsafePointer<UInt8>) in
var srcp = src
let dst = UnsafeMutablePointer<UInt8>.allocate(capacity: dstlen + 1)
var dstp = dst
defer { dst.deallocate() }
let work = UnsafeMutablePointer<UInt8>.allocate(capacity: 8)
defer { work.deallocate() }
while srcleft > 0 {
switch srcleft {
case _ where 5 <= srcleft:
work[7] = srcp[4]
work[6] = srcp[4] >> 5
fallthrough
case 4:
work[6] |= srcp[3] << 3
work[5] = srcp[3] >> 2
work[4] = srcp[3] >> 7
fallthrough
case 3:
work[4] |= srcp[2] << 1
work[3] = srcp[2] >> 4
fallthrough
case 2:
work[3] |= srcp[1] << 4
work[2] = srcp[1] >> 1
work[1] = srcp[1] >> 6
fallthrough
case 1:
work[1] |= srcp[0] << 2
work[0] = srcp[0] >> 3
default:
break
}
for i in 0 ..< Swift.min(8, dstleft) {
dstp[i] = table[Int(work[i] & 0x1f)]
}
if srcleft < 5 {
if padding {
switch srcleft {
case 1:
dstp[2] = "=".utf8.first!
dstp[3] = "=".utf8.first!
fallthrough
case 2:
dstp[4] = "=".utf8.first!
fallthrough
case 3:
dstp[5] = "=".utf8.first!
dstp[6] = "=".utf8.first!
fallthrough
case 4:
dstp[7] = "=".utf8.first!
default:
break
}
}
break
}
srcp += 5
srcleft -= 5
dstp += 8
dstleft -= 8
}
dst[dstlen] = 0
return String(cString: dst)
}
}
}