Skip to content

Commit 97237cf

Browse files
authored
Base32 (#29)
* Add Base32 support and integration tests * Moved Base64 code into Base64.swift
1 parent 9c7648c commit 97237cf

File tree

8 files changed

+1188
-859
lines changed

8 files changed

+1188
-859
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
.build
33
/*.xcodeproj
44
xcuserdata
5+
/.swiftpm
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ExtrasBase64
2+
3+
func run(identifier: String) {
4+
let base32 = "AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674"
5+
var bytes: [UInt8]?
6+
7+
measure(identifier: identifier) {
8+
for _ in 0 ..< 1000 {
9+
bytes = try! Base32.decode(string: base32)
10+
}
11+
12+
return bytes?.count ?? 0
13+
}
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import ExtrasBase64
2+
3+
func run(identifier: String) {
4+
let bytes = Array(UInt8(0) ... UInt8(255))
5+
var base32: String?
6+
7+
measure(identifier: identifier) {
8+
for _ in 0 ..< 1000 {
9+
base32 = Base32.encodeString(bytes: bytes)
10+
}
11+
12+
return base32?.count ?? 0
13+
}
14+
}

Sources/ExtrasBase64/Base32.swift

Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
2+
/// String extensions
3+
public extension String {
4+
init<Buffer: Collection>(base32Encoding bytes: Buffer) where Buffer.Element == UInt8 {
5+
self = Base32.encodeString(bytes: bytes)
6+
}
7+
8+
func base32decoded() throws -> [UInt8] {
9+
try Base32.decode(string: self)
10+
}
11+
}
12+
13+
public enum Base32 {
14+
public enum DecodingError: Swift.Error, Equatable {
15+
case invalidCharacter(UInt8)
16+
}
17+
18+
/// Base32 Encode a buffer to an array of bytes
19+
public static func encodeBytes<Buffer: Collection>(bytes: Buffer) -> [UInt8] where Buffer.Element == UInt8 {
20+
let capacity = (bytes.count * 8 + 4) / 5
21+
22+
let result = bytes.withContiguousStorageIfAvailable { input -> [UInt8] in
23+
[UInt8](unsafeUninitializedCapacity: capacity) { buffer, length in
24+
length = Self._encode(from: input, into: buffer)
25+
}
26+
}
27+
if let result = result {
28+
return result
29+
}
30+
31+
return self.encodeBytes(bytes: Array(bytes))
32+
}
33+
34+
/// Base32 Encode a buffer to a string
35+
public static func encodeString<Buffer: Collection>(bytes: Buffer) -> String where Buffer.Element == UInt8 {
36+
let capacity = (bytes.count * 8 + 4) / 5
37+
38+
#if swift(>=5.3)
39+
if #available(OSX 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) {
40+
let result = bytes.withContiguousStorageIfAvailable { input in
41+
String(unsafeUninitializedCapacity: capacity) { buffer -> Int in
42+
Self._encode(from: input, into: buffer)
43+
}
44+
}
45+
if let result = result {
46+
return result
47+
}
48+
49+
return self.encodeString(bytes: Array(bytes))
50+
} else {
51+
let bytes: [UInt8] = self.encodeBytes(bytes: bytes)
52+
return String(decoding: bytes, as: Unicode.UTF8.self)
53+
}
54+
#else
55+
let bytes: [UInt8] = self.encodeBytes(bytes: bytes)
56+
return String(decoding: bytes, as: Unicode.UTF8.self)
57+
#endif
58+
}
59+
60+
/// Base32 decode string
61+
public static func decode(string encoded: String) throws -> [UInt8] {
62+
let decoded = try encoded.utf8.withContiguousStorageIfAvailable { (characterPointer) -> [UInt8] in
63+
guard characterPointer.count > 0 else {
64+
return []
65+
}
66+
67+
let capacity = (encoded.utf8.count * 5 + 4) / 8
68+
69+
return try characterPointer.withMemoryRebound(to: UInt8.self) { (input) -> [UInt8] in
70+
try [UInt8](unsafeUninitializedCapacity: capacity) { output, length in
71+
length = try Self._decode(from: input, into: output)
72+
}
73+
}
74+
}
75+
76+
if let decoded = decoded {
77+
return decoded
78+
}
79+
80+
var encoded = encoded
81+
encoded.makeContiguousUTF8()
82+
return try Self.decode(string: encoded)
83+
}
84+
85+
public static func decode<Buffer: Collection>(bytes: Buffer) throws -> [UInt8] where Buffer.Element == UInt8 {
86+
guard bytes.count > 0 else {
87+
return []
88+
}
89+
90+
let decoded = try bytes.withContiguousStorageIfAvailable { (input) -> [UInt8] in
91+
let outputLength = ((input.count + 3) / 4) * 3
92+
93+
return try [UInt8](unsafeUninitializedCapacity: outputLength) { output, length in
94+
length = try Self._decode(from: input, into: output)
95+
}
96+
}
97+
98+
if decoded != nil {
99+
return decoded!
100+
}
101+
102+
return try self.decode(bytes: Array(bytes))
103+
}
104+
}
105+
106+
extension Base32 {
107+
private static let decodeTable: [UInt32] = [
108+
/* 00 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
109+
/* 08 */ 0x80, 0x40, 0x40, 0x80, 0x80, 0x40, 0x80, 0x80,
110+
/* 10 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
111+
/* 18 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
112+
/* 20 */ 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
113+
/* 28 */ 0x80, 0x80, 0x80, 0x40, 0x80, 0x80, 0x80, 0x80,
114+
/* 30 */ 0x80, 0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
115+
/* 38 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
116+
/* 40 */ 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
117+
/* 48 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
118+
/* 50 */ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
119+
/* 58 */ 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,
120+
/* 60 */ 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
121+
/* 68 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
122+
/* 60 */ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
123+
/* 68 */ 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,
124+
125+
/* 80 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
126+
/* 88 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
127+
/* 90 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
128+
/* 98 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
129+
/* A0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
130+
/* A8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
131+
/* B0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
132+
/* B8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
133+
/* C0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
134+
/* C8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
135+
/* D0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
136+
/* D8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
137+
/* E0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
138+
/* E8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
139+
/* F0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
140+
/* F8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
141+
]
142+
143+
private static let encodeTable: [UInt8] = [
144+
/* 00 */ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
145+
/* 08 */ 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
146+
/* 10 */ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
147+
/* 18 */ 0x59, 0x5A, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
148+
]
149+
150+
private static func _decode(from input: UnsafeBufferPointer<UInt8>, into output: UnsafeMutableBufferPointer<UInt8>) throws -> Int {
151+
guard input.count != 0 else { return 0 }
152+
153+
return try self.decodeTable.withUnsafeBufferPointer { decodeTable in
154+
var bitsLeft: Int = 0
155+
var buffer: UInt32 = 0
156+
var outputIndex = 0
157+
for i in 0 ..< input.count {
158+
let index = Int(input[i])
159+
let v = decodeTable[index]
160+
// check for unexpected character
161+
guard v != 0x80 else { throw DecodingError.invalidCharacter(input[i]) }
162+
// check for ignorable character
163+
guard v != 0x40 else { continue }
164+
165+
buffer <<= 5
166+
buffer |= v
167+
bitsLeft += 5
168+
if bitsLeft >= 8 {
169+
let result = (buffer >> (bitsLeft - 8))
170+
output[outputIndex] = UInt8(result & 0xFF)
171+
outputIndex += 1
172+
bitsLeft -= 8
173+
}
174+
}
175+
return outputIndex
176+
}
177+
}
178+
179+
private static func _encode(from input: UnsafeBufferPointer<UInt8>, into output: UnsafeMutableBufferPointer<UInt8>) -> Int {
180+
guard input.count != 0 else { return 0 }
181+
182+
return self.encodeTable.withUnsafeBufferPointer { encodeTable in
183+
var outputIndex = 0
184+
var i = 1
185+
var bitsLeft = 8
186+
var buffer = Int(input[0])
187+
while i < input.count {
188+
if bitsLeft < 5 {
189+
buffer <<= 8
190+
buffer |= Int(input[i])
191+
i += 1
192+
bitsLeft += 8
193+
}
194+
let unmaskedIndex = (buffer >> (bitsLeft - 5))
195+
let index = 0x1F & unmaskedIndex
196+
bitsLeft -= 5
197+
output[outputIndex] = encodeTable[index]
198+
outputIndex += 1
199+
}
200+
201+
while bitsLeft > 0 {
202+
if bitsLeft < 5 {
203+
let pad = 5 - bitsLeft
204+
buffer <<= pad
205+
bitsLeft += pad
206+
}
207+
let unmaskedIndex = (buffer >> (bitsLeft - 5))
208+
let index = 0x1F & unmaskedIndex
209+
bitsLeft -= 5
210+
output[outputIndex] = encodeTable[index]
211+
outputIndex += 1
212+
}
213+
return outputIndex
214+
}
215+
}
216+
}

0 commit comments

Comments
 (0)