Refactor Base32 encoder / decoder
This commit is contained in:
parent
c0d5346d88
commit
7664bd83d9
|
@ -9,41 +9,26 @@ import Foundation
|
|||
|
||||
enum Base32 {
|
||||
|
||||
static let crockfordsEncodingTable: [Character] = Array("0123456789ABCDEFGHJKMNPQRSTVWXYZ")
|
||||
static let crockfordsEncodingTable: [UInt8] = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".utf8.map({ $0 })
|
||||
|
||||
static let crockfordsDecodingTable: [Character: UInt8] = [
|
||||
"0": 0x00, "O": 0x00, "o": 0x00,
|
||||
"1": 0x01, "I": 0x01, "i": 0x01, "L": 0x01, "l": 0x01,
|
||||
"2": 0x02,
|
||||
"3": 0x03,
|
||||
"4": 0x04,
|
||||
"5": 0x05,
|
||||
"6": 0x06,
|
||||
"7": 0x07,
|
||||
"8": 0x08,
|
||||
"9": 0x09,
|
||||
"A": 0x0a, "a": 0x0a,
|
||||
"B": 0x0b, "b": 0x0b,
|
||||
"C": 0x0c, "c": 0x0c,
|
||||
"D": 0x0d, "d": 0x0d,
|
||||
"E": 0x0e, "e": 0x0e,
|
||||
"F": 0x0f, "f": 0x0f,
|
||||
"G": 0x10, "g": 0x10,
|
||||
"H": 0x11, "h": 0x11,
|
||||
"J": 0x12, "j": 0x12,
|
||||
"K": 0x13, "k": 0x13,
|
||||
"M": 0x14, "m": 0x14,
|
||||
"N": 0x15, "n": 0x15,
|
||||
"P": 0x16, "p": 0x16,
|
||||
"Q": 0x17, "q": 0x17,
|
||||
"R": 0x18, "r": 0x18,
|
||||
"S": 0x19, "s": 0x19,
|
||||
"T": 0x1a, "t": 0x1a,
|
||||
"V": 0x1b, "v": 0x1b,
|
||||
"W": 0x1c, "w": 0x1c,
|
||||
"X": 0x1d, "x": 0x1d,
|
||||
"Y": 0x1e, "y": 0x1e,
|
||||
"Z": 0x1f, "z": 0x1f
|
||||
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
|
||||
]
|
||||
|
||||
}
|
||||
|
@ -55,37 +40,80 @@ enum Base32Error: Error {
|
|||
extension Data {
|
||||
|
||||
/// Decode Crockford's Base32
|
||||
init?(base32Encoded base32String: String, using table: [Character: UInt8] = Base32.crockfordsDecodingTable) {
|
||||
var str: [Character] = Array(base32String)
|
||||
while let last = str.last, last == "=" {
|
||||
str.removeLast()
|
||||
init?(base32Encoded base32String: String, using table: [UInt8] = Base32.crockfordsDecodingTable) {
|
||||
guard base32String.unicodeScalars.allSatisfy({ $0.isASCII }) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let div = str.count / 8
|
||||
let mod = str.count % 8
|
||||
|
||||
var buffer = Data()
|
||||
|
||||
do {
|
||||
func unwrap(_ value: UInt8?) throws -> UInt8 {
|
||||
guard let value = value else { throw Base32Error.invalidCharacter }
|
||||
return value
|
||||
var base32String = base32String
|
||||
while let last = base32String.last, last == "=" {
|
||||
base32String.removeLast()
|
||||
}
|
||||
guard [0, 2, 4, 5, 7].contains(base32String.count % 8) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
for i in 0 ... div {
|
||||
if i == div, mod == 0 { break }
|
||||
let offset = 8 * i
|
||||
try buffer.append(unwrap(table[str[offset + 0]]) << 3 | unwrap(table[str[offset + 1]]) >> 2)
|
||||
if i == div, mod == 2 { break }
|
||||
try buffer.append(unwrap(table[str[offset + 1]]) << 6 | unwrap(table[str[offset + 2]]) << 1 | unwrap(table[str[offset + 3]]) >> 4)
|
||||
if i == div, mod == 4 { break }
|
||||
try buffer.append(unwrap(table[str[offset + 3]]) << 4 | unwrap(table[str[offset + 4]]) >> 1)
|
||||
if i == div, mod == 5 { break }
|
||||
try buffer.append(unwrap(table[str[offset + 4]]) << 7 | unwrap(table[str[offset + 5]]) << 2 | unwrap(table[str[offset + 6]]) >> 3)
|
||||
if i == div, mod == 7 { break }
|
||||
try buffer.append(unwrap(table[str[offset + 6]]) << 5 | unwrap(table[str[offset + 7]]))
|
||||
let dstlen = base32String.count * 5 / 8
|
||||
|
||||
var buffer = Data(count: dstlen)
|
||||
|
||||
let success: Bool = buffer.withUnsafeMutableBytes { (dst: UnsafeMutablePointer<UInt8>) in
|
||||
base32String.withCString(encodedAs: Unicode.ASCII.self) { (src) in
|
||||
func _strlen(_ str: UnsafePointer<UInt8>) -> Int {
|
||||
var str = str
|
||||
var i = 0
|
||||
while str.pointee != 0 {
|
||||
str += 1
|
||||
i += 1
|
||||
}
|
||||
} catch {
|
||||
return i
|
||||
}
|
||||
|
||||
var srcleft = _strlen(src)
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -93,36 +121,89 @@ extension Data {
|
|||
}
|
||||
|
||||
/// Encode Crockford's Base32
|
||||
func base32EncodedString(using table: [Character] = Base32.crockfordsEncodingTable) -> String {
|
||||
let div = self.count / 5
|
||||
let mod = self.count % 5
|
||||
func base32EncodedString(padding: Bool = true, using table: [UInt8] = Base32.crockfordsEncodingTable) -> String {
|
||||
var srcleft = self.count
|
||||
|
||||
return self.withUnsafeBytes { (bytes: UnsafePointer<UInt8>) in
|
||||
var str = [Character]()
|
||||
var pad = 0
|
||||
let dstlen: Int
|
||||
if padding {
|
||||
dstlen = (self.count + 4) / 5 * 8
|
||||
} else {
|
||||
dstlen = (self.count * 8 + 4) / 5
|
||||
}
|
||||
var dstleft = dstlen
|
||||
|
||||
for i in 0 ... div {
|
||||
if i == div, mod == 0 { break }
|
||||
let offset = 5 * i
|
||||
str.append(table[Int((bytes[offset + 0] >> 3))])
|
||||
str.append(table[Int((bytes[offset + 0] & 0b00000111) << 2 | bytes[offset + 1] >> 6)])
|
||||
if i == div, mod == 1 { pad = 6; break }
|
||||
str.append(table[Int((bytes[offset + 1] & 0b00111110) >> 1)])
|
||||
str.append(table[Int((bytes[offset + 1] & 0b00000001) << 4 | bytes[offset + 2] >> 4)])
|
||||
if i == div, mod == 2 { pad = 4; break }
|
||||
str.append(table[Int((bytes[offset + 2] & 0b00001111) << 1 | bytes[offset + 3] >> 7)])
|
||||
if i == div, mod == 3 { pad = 3; break }
|
||||
str.append(table[Int((bytes[offset + 3] & 0b01111100) >> 2)])
|
||||
str.append(table[Int((bytes[offset + 3] & 0b00000011) << 3 | bytes[offset + 4] >> 5)])
|
||||
if i == div, mod == 4 { pad = 1; break }
|
||||
str.append(table[Int((bytes[offset + 4] & 0b00011111))])
|
||||
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 _ in 0 ..< pad {
|
||||
str.append("=")
|
||||
for i in 0 ..< Swift.min(8, dstleft) {
|
||||
dstp[i] = table[Int(work[i] & 0x1f)]
|
||||
}
|
||||
|
||||
return String(str)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,9 @@ import XCTest
|
|||
|
||||
final class Base32Tests: XCTestCase {
|
||||
|
||||
// MARK: -
|
||||
// MARK: Encode
|
||||
|
||||
func testEncodeBase32() {
|
||||
let expected = "00000001D0YX86C6ZTSZJNXFHDQMCYBQ"
|
||||
|
||||
|
@ -22,6 +25,117 @@ final class Base32Tests: XCTestCase {
|
|||
XCTAssertEqual(expected, data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode1() {
|
||||
let bytes: [UInt8] = [
|
||||
0b11111000, 0b00000000, 0b00000000, 0b00000000, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("Z0000000", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode2() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000111, 0b11000000, 0b00000000, 0b00000000, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("0Z000000", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode3() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00111110, 0b00000000, 0b00000000, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("00Z00000", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode4() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00000001, 0b11110000, 0b00000000, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("000Z0000", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode5() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00000000, 0b00001111, 0b10000000, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("0000Z000", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode6() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00000000, 0b00000000, 0b01111100, 0b00000000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("00000Z00", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode7() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000011, 0b11100000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("000000Z0", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncode8() {
|
||||
let bytes: [UInt8] = [
|
||||
0b00000000, 0b00000000, 0b00000000, 0b00000000, 0b00011111
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("0000000Z", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncodePad1() {
|
||||
let bytes: [UInt8] = [
|
||||
0b10000100
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("GG======", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncodePad2() {
|
||||
let bytes: [UInt8] = [
|
||||
0b10000100, 0b00100001
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("GGGG====", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncodePad3() {
|
||||
let bytes: [UInt8] = [
|
||||
0b10000100, 0b00100001, 0b00001000
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("GGGGG===", data.base32EncodedString())
|
||||
}
|
||||
|
||||
func testEncodePad4() {
|
||||
let bytes: [UInt8] = [
|
||||
0b10000100, 0b00100001, 0b00001000, 0b01000010
|
||||
]
|
||||
let data = Data(bytes: bytes)
|
||||
|
||||
XCTAssertEqual("GGGGGGG=", data.base32EncodedString())
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
// MARK: Decode
|
||||
|
||||
func testDecodeBase32() {
|
||||
let expected: [UInt8] = [
|
||||
0x00, 0x00, 0x00, 0x00, 0x01, 0x68, 0x3D, 0xD4, 0x19, 0x86,
|
||||
|
@ -35,6 +149,52 @@ final class Base32Tests: XCTestCase {
|
|||
XCTAssertEqual(expected, Array(data!))
|
||||
}
|
||||
|
||||
func testDecodeTable() {
|
||||
let table: [Character: UInt8] = [
|
||||
"0": 0x00, "O": 0x00, "o": 0x00,
|
||||
"1": 0x01, "I": 0x01, "i": 0x01, "L": 0x01, "l": 0x01,
|
||||
"2": 0x02,
|
||||
"3": 0x03,
|
||||
"4": 0x04,
|
||||
"5": 0x05,
|
||||
"6": 0x06,
|
||||
"7": 0x07,
|
||||
"8": 0x08,
|
||||
"9": 0x09,
|
||||
"A": 0x0a, "a": 0x0a,
|
||||
"B": 0x0b, "b": 0x0b,
|
||||
"C": 0x0c, "c": 0x0c,
|
||||
"D": 0x0d, "d": 0x0d,
|
||||
"E": 0x0e, "e": 0x0e,
|
||||
"F": 0x0f, "f": 0x0f,
|
||||
"G": 0x10, "g": 0x10,
|
||||
"H": 0x11, "h": 0x11,
|
||||
"J": 0x12, "j": 0x12,
|
||||
"K": 0x13, "k": 0x13,
|
||||
"M": 0x14, "m": 0x14,
|
||||
"N": 0x15, "n": 0x15,
|
||||
"P": 0x16, "p": 0x16,
|
||||
"Q": 0x17, "q": 0x17,
|
||||
"R": 0x18, "r": 0x18,
|
||||
"S": 0x19, "s": 0x19,
|
||||
"T": 0x1a, "t": 0x1a,
|
||||
"V": 0x1b, "v": 0x1b,
|
||||
"W": 0x1c, "w": 0x1c,
|
||||
"X": 0x1d, "x": 0x1d,
|
||||
"Y": 0x1e, "y": 0x1e,
|
||||
"Z": 0x1f, "z": 0x1f
|
||||
]
|
||||
|
||||
for (char, value) in table {
|
||||
let data = Data(base32Encoded: String(char) + "0")
|
||||
XCTAssertNotNil(data)
|
||||
XCTAssertEqual(1, data!.count)
|
||||
XCTAssertEqual(value << 3, data![0])
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: -
|
||||
|
||||
static var allTests = [
|
||||
("testEncodeBase32", testEncodeBase32),
|
||||
("testDecodeBase32", testDecodeBase32)
|
||||
|
|
Loading…
Reference in New Issue