Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add Base32 support and integration tests * Moved Base64 code into Base64.swift
- Loading branch information
1 parent
9c7648c
commit 97237cf
Showing
8 changed files
with
1,188 additions
and
859 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,3 +2,4 @@ | |
.build | ||
/*.xcodeproj | ||
xcuserdata | ||
/.swiftpm |
14 changes: 14 additions & 0 deletions
14
IntegrationTests/tests_04_performance/test_01_resources/test_base32_decoding.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
IntegrationTests/tests_04_performance/test_01_resources/test_base32_encoding.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 | ||
} | ||
} | ||
} |
Oops, something went wrong.