Skip to content

Commit

Permalink
Base32 (#29)
Browse files Browse the repository at this point in the history
* Add Base32 support and integration tests
* Moved Base64 code into Base64.swift
  • Loading branch information
adam-fowler committed Mar 23, 2021
1 parent 9c7648c commit 97237cf
Show file tree
Hide file tree
Showing 8 changed files with 1,188 additions and 859 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -2,3 +2,4 @@
.build
/*.xcodeproj
xcuserdata
/.swiftpm
@@ -0,0 +1,14 @@
import ExtrasBase64

func run(identifier: String) {
let base32 = "AAAQEAYEAUDAOCAJBIFQYDIOB4IBCEQTCQKRMFYYDENBWHA5DYPSAIJCEMSCKJRHFAUSUKZMFUXC6MBRGIZTINJWG44DSOR3HQ6T4P2AIFBEGRCFIZDUQSKKJNGE2TSPKBIVEU2UKVLFOWCZLJNVYXK6L5QGCYTDMRSWMZ3INFVGW3DNNZXXA4LSON2HK5TXPB4XU634PV7H7AEBQKBYJBMGQ6EITCULRSGY5D4QSGJJHFEVS2LZRGM2TOOJ3HU7UCQ2FI5EUWTKPKFJVKV2ZLNOV6YLDMVTWS23NN5YXG5LXPF5X274BQOCYPCMLRWHZDE4VS6MZXHM7UGR2LJ5JVOW27MNTWW33TO55X7A4HROHZHF43T6R2PK5PWO33XP6DY7F47U6X3PP6HZ7L57Z7P674"
var bytes: [UInt8]?

measure(identifier: identifier) {
for _ in 0 ..< 1000 {
bytes = try! Base32.decode(string: base32)
}

return bytes?.count ?? 0
}
}
@@ -0,0 +1,14 @@
import ExtrasBase64

func run(identifier: String) {
let bytes = Array(UInt8(0) ... UInt8(255))
var base32: String?

measure(identifier: identifier) {
for _ in 0 ..< 1000 {
base32 = Base32.encodeString(bytes: bytes)
}

return base32?.count ?? 0
}
}
216 changes: 216 additions & 0 deletions Sources/ExtrasBase64/Base32.swift
@@ -0,0 +1,216 @@

/// String extensions
public extension String {
init<Buffer: Collection>(base32Encoding bytes: Buffer) where Buffer.Element == UInt8 {
self = Base32.encodeString(bytes: bytes)
}

func base32decoded() throws -> [UInt8] {
try Base32.decode(string: self)
}
}

public enum Base32 {
public enum DecodingError: Swift.Error, Equatable {
case invalidCharacter(UInt8)
}

/// Base32 Encode a buffer to an array of bytes
public static func encodeBytes<Buffer: Collection>(bytes: Buffer) -> [UInt8] where Buffer.Element == UInt8 {
let capacity = (bytes.count * 8 + 4) / 5

let result = bytes.withContiguousStorageIfAvailable { input -> [UInt8] in
[UInt8](unsafeUninitializedCapacity: capacity) { buffer, length in
length = Self._encode(from: input, into: buffer)
}
}
if let result = result {
return result
}

return self.encodeBytes(bytes: Array(bytes))
}

/// Base32 Encode a buffer to a string
public static func encodeString<Buffer: Collection>(bytes: Buffer) -> String where Buffer.Element == UInt8 {
let capacity = (bytes.count * 8 + 4) / 5

#if swift(>=5.3)
if #available(OSX 11.0, iOS 14.0, tvOS 14.0, watchOS 7.0, *) {
let result = bytes.withContiguousStorageIfAvailable { input in
String(unsafeUninitializedCapacity: capacity) { buffer -> Int in
Self._encode(from: input, into: buffer)
}
}
if let result = result {
return result
}

return self.encodeString(bytes: Array(bytes))
} else {
let bytes: [UInt8] = self.encodeBytes(bytes: bytes)
return String(decoding: bytes, as: Unicode.UTF8.self)
}
#else
let bytes: [UInt8] = self.encodeBytes(bytes: bytes)
return String(decoding: bytes, as: Unicode.UTF8.self)
#endif
}

/// Base32 decode string
public static func decode(string encoded: String) throws -> [UInt8] {
let decoded = try encoded.utf8.withContiguousStorageIfAvailable { (characterPointer) -> [UInt8] in
guard characterPointer.count > 0 else {
return []
}

let capacity = (encoded.utf8.count * 5 + 4) / 8

return try characterPointer.withMemoryRebound(to: UInt8.self) { (input) -> [UInt8] in
try [UInt8](unsafeUninitializedCapacity: capacity) { output, length in
length = try Self._decode(from: input, into: output)
}
}
}

if let decoded = decoded {
return decoded
}

var encoded = encoded
encoded.makeContiguousUTF8()
return try Self.decode(string: encoded)
}

public static func decode<Buffer: Collection>(bytes: Buffer) throws -> [UInt8] where Buffer.Element == UInt8 {
guard bytes.count > 0 else {
return []
}

let decoded = try bytes.withContiguousStorageIfAvailable { (input) -> [UInt8] in
let outputLength = ((input.count + 3) / 4) * 3

return try [UInt8](unsafeUninitializedCapacity: outputLength) { output, length in
length = try Self._decode(from: input, into: output)
}
}

if decoded != nil {
return decoded!
}

return try self.decode(bytes: Array(bytes))
}
}

extension Base32 {
private static let decodeTable: [UInt32] = [
/* 00 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 08 */ 0x80, 0x40, 0x40, 0x80, 0x80, 0x40, 0x80, 0x80,
/* 10 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 18 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 20 */ 0x40, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 28 */ 0x80, 0x80, 0x80, 0x40, 0x80, 0x80, 0x80, 0x80,
/* 30 */ 0x80, 0x80, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
/* 38 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 40 */ 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
/* 48 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
/* 50 */ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
/* 58 */ 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 60 */ 0x80, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
/* 68 */ 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
/* 60 */ 0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16,
/* 68 */ 0x17, 0x18, 0x19, 0x80, 0x80, 0x80, 0x80, 0x80,

/* 80 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 88 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 90 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* 98 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* A0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* A8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* B0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* B8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* C0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* C8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* D0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* D8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* E0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* E8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* F0 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
/* F8 */ 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
]

private static let encodeTable: [UInt8] = [
/* 00 */ 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
/* 08 */ 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
/* 10 */ 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
/* 18 */ 0x59, 0x5A, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
]

private static func _decode(from input: UnsafeBufferPointer<UInt8>, into output: UnsafeMutableBufferPointer<UInt8>) throws -> Int {
guard input.count != 0 else { return 0 }

return try self.decodeTable.withUnsafeBufferPointer { decodeTable in
var bitsLeft: Int = 0
var buffer: UInt32 = 0
var outputIndex = 0
for i in 0 ..< input.count {
let index = Int(input[i])
let v = decodeTable[index]
// check for unexpected character
guard v != 0x80 else { throw DecodingError.invalidCharacter(input[i]) }
// check for ignorable character
guard v != 0x40 else { continue }

buffer <<= 5
buffer |= v
bitsLeft += 5
if bitsLeft >= 8 {
let result = (buffer >> (bitsLeft - 8))
output[outputIndex] = UInt8(result & 0xFF)
outputIndex += 1
bitsLeft -= 8
}
}
return outputIndex
}
}

private static func _encode(from input: UnsafeBufferPointer<UInt8>, into output: UnsafeMutableBufferPointer<UInt8>) -> Int {
guard input.count != 0 else { return 0 }

return self.encodeTable.withUnsafeBufferPointer { encodeTable in
var outputIndex = 0
var i = 1
var bitsLeft = 8
var buffer = Int(input[0])
while i < input.count {
if bitsLeft < 5 {
buffer <<= 8
buffer |= Int(input[i])
i += 1
bitsLeft += 8
}
let unmaskedIndex = (buffer >> (bitsLeft - 5))
let index = 0x1F & unmaskedIndex
bitsLeft -= 5
output[outputIndex] = encodeTable[index]
outputIndex += 1
}

while bitsLeft > 0 {
if bitsLeft < 5 {
let pad = 5 - bitsLeft
buffer <<= pad
bitsLeft += pad
}
let unmaskedIndex = (buffer >> (bitsLeft - 5))
let index = 0x1F & unmaskedIndex
bitsLeft -= 5
output[outputIndex] = encodeTable[index]
outputIndex += 1
}
return outputIndex
}
}
}

0 comments on commit 97237cf

Please sign in to comment.