diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index ac23101e0..757edcb76 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -10,10 +10,20 @@ // //===----------------------------------------------------------------------===// -private enum Base64Error: Error { - case invalidElementCount - case cannotDecode -} +#if canImport(Darwin) +import Darwin +#elseif canImport(Bionic) +import Bionic +#elseif canImport(Glibc) +import Glibc +#elseif canImport(Musl) +import Musl +#elseif os(Windows) +import CRT +import WinSDK +#elseif os(WASI) +import WASILibc +#endif @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension Data { @@ -26,21 +36,10 @@ extension Data { /// - parameter base64String: The string to parse. /// - parameter options: Encoding options. Default value is `[]`. public init?(base64Encoded base64String: __shared String, options: Base64DecodingOptions = []) { -#if FOUNDATION_FRAMEWORK - if let d = NSData(base64Encoded: base64String, options: NSData.Base64DecodingOptions(rawValue: options.rawValue)) { - self.init(referencing: d) - } else { + guard let result = try? Base64.decode(string: base64String, options: options) else { return nil } -#else - var encoded = base64String - let decoded = encoded.withUTF8 { - // String won't pass an empty buffer with a `nil` `baseAddress`. - Data(decodingBase64: BufferView(unsafeBufferPointer: $0)!, options: options) - } - guard let decoded else { return nil } - self = decoded -#endif + self = result } /// Initialize a `Data` from a Base-64, UTF-8 encoded `Data`. @@ -50,39 +49,10 @@ extension Data { /// - parameter base64Data: Base-64, UTF-8 encoded input data. /// - parameter options: Decoding options. Default value is `[]`. public init?(base64Encoded base64Data: __shared Data, options: Base64DecodingOptions = []) { -#if FOUNDATION_FRAMEWORK - if let d = NSData(base64Encoded: base64Data, options: NSData.Base64DecodingOptions(rawValue: options.rawValue)) { - self.init(referencing: d) - } else { + guard let result = try? Base64.decode(data: base64Data, options: options) else { return nil } -#else - let decoded = base64Data.withBufferView { - Data(decodingBase64: $0, options: options) - } - guard let decoded else { return nil } - self = decoded -#endif - } - - init?(decodingBase64 bytes: borrowing BufferView, options: Base64DecodingOptions = []) { - guard bytes.count.isMultiple(of: 4) || options.contains(.ignoreUnknownCharacters) - else { return nil } - - // Every 4 valid ASCII bytes maps to 3 output bytes: (bytes.count * 3)/4 - let capacity = (bytes.count * 3) >> 2 - // A non-trapping version of the calculation goes like this: - // let (q, r) = bytes.count.quotientAndRemainder(dividingBy: 4) - // let capacity = (q * 3) + (r==0 ? 0 : r-1) - let decoded = try? Data( - capacity: capacity, - initializingWith: { //FIXME: should work with borrowed `bytes` - [bytes = copy bytes] in - try Data.base64DecodeBytes(bytes, &$0, options: options) - } - ) - guard let decoded else { return nil } - self = decoded + self = result } // MARK: - Create base64 @@ -102,110 +72,6 @@ extension Data { public func base64EncodedData(options: Base64EncodingOptions = []) -> Data { Base64.encodeToData(bytes: self, options: options) } - - // MARK: - Internal Helpers - - /** - This method decodes Base64-encoded data. - - If the input contains any bytes that are not valid Base64 characters, - this will throw a `Base64Error`. - - - parameter bytes: The Base64 bytes - - parameter output: An OutputBuffer to be filled with decoded bytes - - parameter options: Options for handling invalid input - - throws: When decoding fails - */ - static func base64DecodeBytes( - _ bytes: borrowing BufferView, _ output: inout OutputBuffer, options: Base64DecodingOptions = [] - ) throws { - guard bytes.count.isMultiple(of: 4) || options.contains(.ignoreUnknownCharacters) - else { throw Base64Error.invalidElementCount } - - // This table maps byte values 0-127, input bytes >127 are always invalid. - // Map the ASCII characters "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -> 0...63 - // Map '=' (ASCII 61) to 0x40. - // All other values map to 0x7f. This allows '=' and invalid bytes to be checked together by testing bit 6 (0x40). - let base64Decode: StaticString = """ -\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\ -\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\ -\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\u{3e}\u{7f}\u{7f}\u{7f}\u{3f}\ -\u{34}\u{35}\u{36}\u{37}\u{38}\u{39}\u{3a}\u{3b}\u{3c}\u{3d}\u{7f}\u{7f}\u{7f}\u{40}\u{7f}\u{7f}\ -\u{7f}\u{00}\u{01}\u{02}\u{03}\u{04}\u{05}\u{06}\u{07}\u{08}\u{09}\u{0a}\u{0b}\u{0c}\u{0d}\u{0e}\ -\u{0f}\u{10}\u{11}\u{12}\u{13}\u{14}\u{15}\u{16}\u{17}\u{18}\u{19}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f}\ -\u{7f}\u{1a}\u{1b}\u{1c}\u{1d}\u{1e}\u{1f}\u{20}\u{21}\u{22}\u{23}\u{24}\u{25}\u{26}\u{27}\u{28}\ -\u{29}\u{2a}\u{2b}\u{2c}\u{2d}\u{2e}\u{2f}\u{30}\u{31}\u{32}\u{33}\u{7f}\u{7f}\u{7f}\u{7f}\u{7f} -""" - assert(base64Decode.isASCII) - assert(base64Decode.utf8CodeUnitCount == 128) - assert(base64Decode.hasPointerRepresentation) - - let ignoreUnknown = options.contains(.ignoreUnknownCharacters) - - var currentByte: UInt8 = 0 - var validCharacterCount = 0 - var paddingCount = 0 - var index = 0 - - for base64Char in bytes { - var value: UInt8 = 0 - - var invalid = false - if base64Char >= base64Decode.utf8CodeUnitCount { - invalid = true - } else { - value = base64Decode.utf8Start[Int(base64Char)] - if value & 0x40 == 0x40 { // Input byte is either '=' or an invalid value. - if value == 0x7f { - invalid = true - } else if value == 0x40 { // '=' padding at end of input. - paddingCount += 1 - continue - } - } - } - - if invalid { - if ignoreUnknown { - continue - } else { - throw Base64Error.cannotDecode - } - } - validCharacterCount += 1 - - // Padding found in the middle of the sequence is invalid. - if paddingCount > 0 { - throw Base64Error.cannotDecode - } - - switch index { - case 0: - currentByte = (value << 2) - case 1: - currentByte |= (value >> 4) - output.appendElement(currentByte) - currentByte = (value << 4) - case 2: - currentByte |= (value >> 2) - output.appendElement(currentByte) - currentByte = (value << 6) - case 3: - currentByte |= value - output.appendElement(currentByte) - index = -1 - default: - fatalError("Invalid state") - } - - index += 1 - } - - guard (validCharacterCount + paddingCount) % 4 == 0 else { - // Invalid character count of valid input characters. - throw Base64Error.cannotDecode - } - } } // This base64 implementation is heavily inspired by: @@ -346,7 +212,7 @@ extension Base64 { return self._encodeWithLineBreaks(input: input, buffer: buffer, length: &length, options: options) } - let omitPaddingCharacter = false // options.contains(.omitPaddingCharacter) + let omitPaddingCharacter = false Self.withUnsafeEncodingTablesAsBufferPointers(options: options) { (e0, e1) throws(Never) -> Void in let to = input.count / 3 * 3 @@ -547,3 +413,642 @@ extension Base64 { } } } + +// MARK: - Decoding - + +extension Base64 { + + enum DecodingError: Error, Equatable { + case invalidLength + case invalidCharacter(UInt8) + case unexpectedPaddingCharacter + case unexpectedEnd + } + + static func decode(string encoded: String, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data { + let result = encoded.utf8.withContiguousStorageIfAvailable { bufferPointer in + // `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need + // to use Result to get the error out without allocation for the error. + Result(catching: { () throws(DecodingError) -> Data in + try Self._decodeToData(from: bufferPointer, options: options) + }) + } + + if let result { + return try result.get() + } + + var encoded = encoded + encoded.makeContiguousUTF8() + return try Self.decode(string: encoded, options: options) + } + + static func decode(data encoded: Data, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data? { + let result = encoded.withContiguousStorageIfAvailable { bufferPointer in + // `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need + // to use Result to get the error out without allocation for the error. + Result(catching: { () throws(DecodingError) -> Data in + try Self._decodeToData(from: bufferPointer, options: options) + }) + } + + if let result { + return try result.get() + } + + return try Self.decode(bytes: Array(encoded), options: options) + } + + static func decode(bytes: Buffer, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data where Buffer.Element == UInt8 { + guard bytes.count > 0 else { + return Data() + } + + let result = bytes.withContiguousStorageIfAvailable { bufferPointer in + // `withContiguousStorageIfAvailable` sadly does not support typed throws, so we need + // to use Result to get the error out without allocation for the error. + Result(catching: { () throws(DecodingError) -> Data in + try Self._decodeToData(from: bufferPointer, options: options) + }) + } + + if let result { + return try result.get() + } + + return try self.decode(bytes: Array(bytes), options: options) + } + + static func _decodeToData(from inBuffer: UnsafeBufferPointer, options: Data.Base64DecodingOptions) throws(DecodingError) -> Data { + guard inBuffer.count > 0 else { + return Data() + } + + let outputLength = ((inBuffer.count + 3) / 4) * 3 + + let pointer = malloc(outputLength) + let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) + let target = UnsafeMutableBufferPointer(start: other, count: outputLength) + var length = outputLength + if options.contains(.ignoreUnknownCharacters) { + try Self._decodeIgnoringErrors(from: inBuffer, into: target, length: &length, options: options) + } else { + // for whatever reason I can see this being 10% faster for larger payloads. Maybe better + // branch prediction? + try self._decode(from: inBuffer, into: target, length: &length, options: options) + } + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } + + static func _decode( + from inBuffer: UnsafeBufferPointer, + into outBuffer: UnsafeMutableBufferPointer, + length: inout Int, + options: Data.Base64DecodingOptions + ) throws(DecodingError) { + guard let lastNonPaddedIndex = inBuffer.lastIndex(where: { $0 != UInt8(ascii: "=") }) else { + if inBuffer.count >= 4 { + outBuffer[0] = 0 + length = 1 + return + } else { + throw DecodingError.invalidLength + } + } + let base64NonPaddedLength = lastNonPaddedIndex + 1 + let bytesToParseLength = if base64NonPaddedLength % 4 == 0 { + (base64NonPaddedLength / 4) * 4 + } else { + (base64NonPaddedLength / 4) * 4 + 4 + } + if bytesToParseLength > inBuffer.count { + throw DecodingError.invalidLength + } + + let outputLength = ((bytesToParseLength + 3) / 4) * 3 + let fullchunks = bytesToParseLength / 4 - 1 + + guard outBuffer.count >= outputLength else { + preconditionFailure("Expected the out buffer to be at least as long as outputLength") + } + + try Self.withUnsafeDecodingTablesAsBufferPointers(options: options) { (d0, d1, d2, d3) throws(DecodingError) in + var outIndex = 0 + if fullchunks > 0 { + for chunk in 0 ..< fullchunks { + let inIndex = chunk * 4 + let a0 = inBuffer[inIndex] + let a1 = inBuffer[inIndex + 1] + let a2 = inBuffer[inIndex + 2] + let a3 = inBuffer[inIndex + 3] + var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)] + + if x >= Self.badCharacter { + // TODO: Inspect characters here better + throw DecodingError.invalidCharacter(inBuffer[inIndex]) + } + + withUnsafePointer(to: &x) { ptr in + ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in + outBuffer[outIndex] = newPtr[0] + outBuffer[outIndex + 1] = newPtr[1] + outBuffer[outIndex + 2] = newPtr[2] + outIndex += 3 + } + } + } + } + + // inIndex is the first index in the last chunk + let inIndex = fullchunks * 4 + let a0 = inBuffer[inIndex] + let a1 = inBuffer[inIndex + 1] + var a2: UInt8? + var a3: UInt8? + if inIndex + 2 < inBuffer.count, inBuffer[inIndex + 2] != Self.encodePaddingCharacter { + a2 = inBuffer[inIndex + 2] + } + if inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter { + a3 = inBuffer[inIndex + 3] + } + + var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2 ?? 65)] | d3[Int(a3 ?? 65)] + if x >= Self.badCharacter { + // TODO: Inspect characters here better + throw DecodingError.invalidCharacter(inBuffer[inIndex]) + } + + withUnsafePointer(to: &x) { ptr in + ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in + outBuffer[outIndex] = newPtr[0] + outIndex += 1 + if a2 != nil { + outBuffer[outIndex] = newPtr[1] + outIndex += 1 + } + if a3 != nil { + outBuffer[outIndex] = newPtr[2] + outIndex += 1 + } + } + } + + length = outIndex + } + } + + static func _decodeIgnoringErrors( + from inBuffer: UnsafeBufferPointer, + into outBuffer: UnsafeMutableBufferPointer, + length: inout Int, + options: Data.Base64DecodingOptions + ) throws(DecodingError) { + assert(options.contains(.ignoreUnknownCharacters)) + + let outputLength = ((inBuffer.count + 3) / 4) * 3 + guard outBuffer.count >= outputLength else { + preconditionFailure("Expected the out buffer to be at least as long as outputLength") + } + + try Self.withUnsafeDecodingTablesAsBufferPointers(options: options) { (d0, d1, d2, d3) throws(DecodingError) in + var outIndex = 0 + var inIndex = 0 + + fastLoop: while inIndex + 3 < inBuffer.count { + let a0 = inBuffer[inIndex] + let a1 = inBuffer[inIndex &+ 1] + let a2 = inBuffer[inIndex &+ 2] + let a3 = inBuffer[inIndex &+ 3] + var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)] + + if x >= Self.badCharacter { + if a3 == Self.encodePaddingCharacter || a2 == Self.encodePaddingCharacter || a1 == Self.encodePaddingCharacter || a0 == Self.encodePaddingCharacter { + break fastLoop // the loop + } + + // error fast path. we assume that illeagal errors are at the boundary. + // lets skip them and then return to fast mode! + if !self.isValidBase64Byte(a0, options: options) { + if !self.isValidBase64Byte(a1, options: options) { + if !self.isValidBase64Byte(a2, options: options) { + if !self.isValidBase64Byte(a3, options: options) { + inIndex &+= 4 + continue + } else { + inIndex &+= 3 + continue + } + } else { + inIndex &+= 2 + continue + } + } else { + inIndex &+= 1 + continue + } + } + + // error slow path... the first character is valid base64 + let b0 = a0 + var b1: UInt8? = nil + var b2: UInt8? = nil + var b3: UInt8? = nil + let startIndex = inIndex + inIndex &+= 1 + scanForValidCharacters: while inIndex < inBuffer.count { + guard self.isValidBase64Byte(inBuffer[inIndex], options: options) else { + if inBuffer[inIndex] == Self.encodePaddingCharacter { + inIndex = startIndex + break fastLoop + } + inIndex &+= 1 + continue scanForValidCharacters + } + + defer { inIndex &+= 1 } + + if b1 == nil { + b1 = inBuffer[inIndex] + } else if b2 == nil { + b2 = inBuffer[inIndex] + } else if b3 == nil { + b3 = inBuffer[inIndex] + break scanForValidCharacters + } + } + + guard let b1, let b2, let b3 else { + throw DecodingError.invalidLength + } + + x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)] + + } else { + inIndex &+= 4 + } + + withUnsafePointer(to: &x) { ptr in + ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in + outBuffer[outIndex] = newPtr[0] + outBuffer[outIndex &+ 1] = newPtr[1] + outBuffer[outIndex &+ 2] = newPtr[2] + outIndex &+= 3 + } + } + } + + if inIndex == inBuffer.count { + // all done! + length = outIndex + return + } + + guard inIndex + 3 < inBuffer.count else { + // ensure that all remaining characters are unknown or padding + try Self.validateRemainingBytesAreInvalidOrPadding(in: inBuffer, from: inIndex, options: options) + if outIndex == 0 && inBuffer.count > 0 { throw DecodingError.invalidLength } + length = outIndex + return + } + + let a0 = inBuffer[inIndex] + let a1 = inBuffer[inIndex + 1] + var a2: UInt8 = 65 + var a3: UInt8 = 65 + var padding2 = false + var padding3 = false + + if inBuffer[inIndex + 2] == Self.encodePaddingCharacter { + padding2 = true + } else { + a2 = inBuffer[inIndex + 2] + } + if inBuffer[inIndex + 3] == Self.encodePaddingCharacter { + padding3 = true + } else { + if padding2 && self.isValidBase64Byte(inBuffer[inIndex + 3], options: options) { + throw DecodingError.unexpectedPaddingCharacter + } + a3 = inBuffer[inIndex + 3] + } + + var x: UInt32 = d0[Int(a0)] | d1[Int(a1)] | d2[Int(a2)] | d3[Int(a3)] + if x >= Self.badCharacter { + var b0: UInt8? = nil + var b1: UInt8? = nil + var b2: UInt8? = nil + var b3: UInt8? = nil + + scanForValidCharacters: while inIndex < inBuffer.count { + defer { inIndex &+= 1 } + let value = inBuffer[inIndex] + if self.isValidBase64Byte(value, options: options) { + if b0 == nil { + b0 = value + } else if b1 == nil { + b1 = value + } else if b2 == nil { + b2 = value + } else if b3 == nil { + if padding2 { throw DecodingError.unexpectedPaddingCharacter } + b3 = value + break scanForValidCharacters + } + } else if value == Self.encodePaddingCharacter { + if b0 == nil { + try Self.validateRemainingBytesAreInvalidOrPadding(in: inBuffer, from: inIndex, options: options) + if outIndex == 0 && inBuffer.count > 0 { throw DecodingError.invalidLength } + length = outIndex + return + } else if b1 == nil { + throw DecodingError.invalidLength + } else if b2 == nil { + padding2 = true + b2 = 65 + } else if b3 == nil { + padding3 = true + b3 = 65 + break scanForValidCharacters + } + } + } + + guard let b0, let b1, let b2, let b3 else { + if b0 == nil { + length = outIndex + return + } + throw DecodingError.invalidLength + } + + x = d0[Int(b0)] | d1[Int(b1)] | d2[Int(b2)] | d3[Int(b3)] + assert(x < Self.badCharacter) + } + + withUnsafePointer(to: &x) { ptr in + ptr.withMemoryRebound(to: UInt8.self, capacity: 4) { newPtr in + outBuffer[outIndex] = newPtr[0] + outIndex += 1 + if !padding2 { + outBuffer[outIndex] = newPtr[1] + outIndex += 1 + } + if !padding3 { + outBuffer[outIndex] = newPtr[2] + outIndex += 1 + } + } + } + + length = outIndex + } + } + + static func validateRemainingBytesAreInvalidOrPadding( + in inBuffer: UnsafeBufferPointer, + from index: Int, + options: Data.Base64DecodingOptions + ) throws(DecodingError) { + var inIndex = index + // ensure that all remaining characters are unknown or padding + while inIndex < inBuffer.count { + let value = inBuffer[inIndex] + if self.isValidBase64Byte(value, options: options) { + throw DecodingError.invalidCharacter(inBuffer[inIndex]) + } + inIndex &+= 1 + } + } + + static func withUnsafeDecodingTablesAsBufferPointers(options: Data.Base64DecodingOptions, _ body: (UnsafeBufferPointer, UnsafeBufferPointer, UnsafeBufferPointer, UnsafeBufferPointer) throws(E) -> R) throws(E) -> R { + let decoding0 = Self.decoding0 + let decoding1 = Self.decoding1 + let decoding2 = Self.decoding2 + let decoding3 = Self.decoding3 + + assert(decoding0.count == 256) + assert(decoding1.count == 256) + assert(decoding2.count == 256) + assert(decoding3.count == 256) + + // Workaround that `withUnsafeBufferPointer` started to support typed throws in Swift 6.1 + let result = decoding0.withUnsafeBufferPointer { d0 -> Result in + decoding1.withUnsafeBufferPointer { d1 -> Result in + decoding2.withUnsafeBufferPointer { d2 -> Result in + decoding3.withUnsafeBufferPointer { d3 -> Result in + Result { () throws(E) -> R in + try body(d0, d1, d2, d3) + } + } + } + } + } + + return try result.get() + } + + static func isValidBase64Byte(_ byte: UInt8, options: Data.Base64DecodingOptions) -> Bool { + switch byte { + case UInt8(ascii: "A")...UInt8(ascii: "Z"), + UInt8(ascii: "a")...UInt8(ascii: "z"), + UInt8(ascii: "0")...UInt8(ascii: "9"): + true + + case UInt8(ascii: "-"), UInt8(ascii: "_"): + false // options.contains(.base64UrlAlphabet) + + case UInt8(ascii: "/"), UInt8(ascii: "+"): + true // !options.contains(.base64UrlAlphabet) + + default: + false + } + } + + static let badCharacter: UInt32 = 0x01FF_FFFF + + static let decoding0: [UInt32] = [ + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0000_00F8, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_00FC, + 0x0000_00D0, 0x0000_00D4, 0x0000_00D8, 0x0000_00DC, 0x0000_00E0, 0x0000_00E4, + 0x0000_00E8, 0x0000_00EC, 0x0000_00F0, 0x0000_00F4, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, + 0x0000_0004, 0x0000_0008, 0x0000_000C, 0x0000_0010, 0x0000_0014, 0x0000_0018, + 0x0000_001C, 0x0000_0020, 0x0000_0024, 0x0000_0028, 0x0000_002C, 0x0000_0030, + 0x0000_0034, 0x0000_0038, 0x0000_003C, 0x0000_0040, 0x0000_0044, 0x0000_0048, + 0x0000_004C, 0x0000_0050, 0x0000_0054, 0x0000_0058, 0x0000_005C, 0x0000_0060, + 0x0000_0064, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0000_0068, 0x0000_006C, 0x0000_0070, 0x0000_0074, 0x0000_0078, + 0x0000_007C, 0x0000_0080, 0x0000_0084, 0x0000_0088, 0x0000_008C, 0x0000_0090, + 0x0000_0094, 0x0000_0098, 0x0000_009C, 0x0000_00A0, 0x0000_00A4, 0x0000_00A8, + 0x0000_00AC, 0x0000_00B0, 0x0000_00B4, 0x0000_00B8, 0x0000_00BC, 0x0000_00C0, + 0x0000_00C4, 0x0000_00C8, 0x0000_00CC, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + ] + + static let decoding1: [UInt32] = [ + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0000_E003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_F003, + 0x0000_4003, 0x0000_5003, 0x0000_6003, 0x0000_7003, 0x0000_8003, 0x0000_9003, + 0x0000_A003, 0x0000_B003, 0x0000_C003, 0x0000_D003, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, + 0x0000_1000, 0x0000_2000, 0x0000_3000, 0x0000_4000, 0x0000_5000, 0x0000_6000, + 0x0000_7000, 0x0000_8000, 0x0000_9000, 0x0000_A000, 0x0000_B000, 0x0000_C000, + 0x0000_D000, 0x0000_E000, 0x0000_F000, 0x0000_0001, 0x0000_1001, 0x0000_2001, + 0x0000_3001, 0x0000_4001, 0x0000_5001, 0x0000_6001, 0x0000_7001, 0x0000_8001, + 0x0000_9001, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0000_A001, 0x0000_B001, 0x0000_C001, 0x0000_D001, 0x0000_E001, + 0x0000_F001, 0x0000_0002, 0x0000_1002, 0x0000_2002, 0x0000_3002, 0x0000_4002, + 0x0000_5002, 0x0000_6002, 0x0000_7002, 0x0000_8002, 0x0000_9002, 0x0000_A002, + 0x0000_B002, 0x0000_C002, 0x0000_D002, 0x0000_E002, 0x0000_F002, 0x0000_0003, + 0x0000_1003, 0x0000_2003, 0x0000_3003, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + ] + + static let decoding2: [UInt32] = [ + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0080_0F00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x00C0_0F00, + 0x0000_0D00, 0x0040_0D00, 0x0080_0D00, 0x00C0_0D00, 0x0000_0E00, 0x0040_0E00, + 0x0080_0E00, 0x00C0_0E00, 0x0000_0F00, 0x0040_0F00, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, + 0x0040_0000, 0x0080_0000, 0x00C0_0000, 0x0000_0100, 0x0040_0100, 0x0080_0100, + 0x00C0_0100, 0x0000_0200, 0x0040_0200, 0x0080_0200, 0x00C0_0200, 0x0000_0300, + 0x0040_0300, 0x0080_0300, 0x00C0_0300, 0x0000_0400, 0x0040_0400, 0x0080_0400, + 0x00C0_0400, 0x0000_0500, 0x0040_0500, 0x0080_0500, 0x00C0_0500, 0x0000_0600, + 0x0040_0600, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x0080_0600, 0x00C0_0600, 0x0000_0700, 0x0040_0700, 0x0080_0700, + 0x00C0_0700, 0x0000_0800, 0x0040_0800, 0x0080_0800, 0x00C0_0800, 0x0000_0900, + 0x0040_0900, 0x0080_0900, 0x00C0_0900, 0x0000_0A00, 0x0040_0A00, 0x0080_0A00, + 0x00C0_0A00, 0x0000_0B00, 0x0040_0B00, 0x0080_0B00, 0x00C0_0B00, 0x0000_0C00, + 0x0040_0C00, 0x0080_0C00, 0x00C0_0C00, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + ] + + static let decoding3: [UInt32] = [ + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x003E_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x003F_0000, + 0x0034_0000, 0x0035_0000, 0x0036_0000, 0x0037_0000, 0x0038_0000, 0x0039_0000, + 0x003A_0000, 0x003B_0000, 0x003C_0000, 0x003D_0000, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x0000_0000, + 0x0001_0000, 0x0002_0000, 0x0003_0000, 0x0004_0000, 0x0005_0000, 0x0006_0000, + 0x0007_0000, 0x0008_0000, 0x0009_0000, 0x000A_0000, 0x000B_0000, 0x000C_0000, + 0x000D_0000, 0x000E_0000, 0x000F_0000, 0x0010_0000, 0x0011_0000, 0x0012_0000, + 0x0013_0000, 0x0014_0000, 0x0015_0000, 0x0016_0000, 0x0017_0000, 0x0018_0000, + 0x0019_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x001A_0000, 0x001B_0000, 0x001C_0000, 0x001D_0000, 0x001E_0000, + 0x001F_0000, 0x0020_0000, 0x0021_0000, 0x0022_0000, 0x0023_0000, 0x0024_0000, + 0x0025_0000, 0x0026_0000, 0x0027_0000, 0x0028_0000, 0x0029_0000, 0x002A_0000, + 0x002B_0000, 0x002C_0000, 0x002D_0000, 0x002E_0000, 0x002F_0000, 0x0030_0000, + 0x0031_0000, 0x0032_0000, 0x0033_0000, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, 0x01FF_FFFF, + ] +} diff --git a/Sources/FoundationEssentials/JSON/JSONDecoder.swift b/Sources/FoundationEssentials/JSON/JSONDecoder.swift index fe0043ce1..f3ede1670 100644 --- a/Sources/FoundationEssentials/JSON/JSONDecoder.swift +++ b/Sources/FoundationEssentials/JSON/JSONDecoder.swift @@ -679,7 +679,7 @@ extension JSONDecoderImpl: Decoder { var data: Data? if isSimple { data = withBuffer(for: region) { buffer, _ in - Data(decodingBase64: buffer) + try? Base64.decode(bytes: buffer) } } if data == nil { diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 2ac55047c..ef820dbab 100644 --- a/Tests/FoundationEssentialsTests/DataTests.swift +++ b/Tests/FoundationEssentialsTests/DataTests.swift @@ -1986,6 +1986,36 @@ extension DataTests { XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: " AQIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "A QIDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQ IDBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQI DBA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQID BA==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDB A==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA ==", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA= =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4]), Data(base64Encoded: "AQIDBA== ", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters)) @@ -1996,6 +2026,36 @@ extension DataTests { XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: " AQIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "A QIDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQ IDBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQI DBAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQID BAU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDB AU=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBA U=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU =", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5]), Data(base64Encoded: "AQIDBAU= ", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters)) @@ -2005,6 +2065,36 @@ extension DataTests { XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters)) XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters)) + + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: " AQIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "A QIDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQ IDBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQI DBAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQID BAUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDB AUG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBA UG", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAU G", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data([1, 2, 3, 4, 5, 6]), Data(base64Encoded: "AQIDBAUG ", options: .ignoreUnknownCharacters)) } func test_base64Decode_test1MBDataGoing0to255OverAndOver() { @@ -2030,7 +2120,68 @@ extension DataTests { func test_base64Data_bad() { XCTAssertNil(Data(base64Encoded: "signature-not-base64-encoded")) } - + + func test_base64Decode_MorePaddingThanNecessary() { + XCTAssertNil(Data(base64Encoded: "=")) + XCTAssertNil(Data(base64Encoded: "==")) + XCTAssertNil(Data(base64Encoded: "===")) + for x in 4..<1000 { + XCTAssertEqual(Data(base64Encoded: String(repeating: "=", count: x)), Data([0])) + } + + XCTAssertEqual(Data(base64Encoded: "AAAA"), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA="), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA=="), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA==="), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA===="), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA="), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA=="), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA==="), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA===="), Data([0, 0])) + XCTAssertNil(Data(base64Encoded: "AA=")) + XCTAssertEqual(Data(base64Encoded: "AA=="), Data([0])) + XCTAssertEqual(Data(base64Encoded: "AA==="), Data([0])) + XCTAssertEqual(Data(base64Encoded: "AA===="), Data([0])) + XCTAssertNil(Data(base64Encoded: "A=")) + XCTAssertNil(Data(base64Encoded: "A==")) + XCTAssertNil(Data(base64Encoded: "A===")) + XCTAssertNil(Data(base64Encoded: "A====")) + } + + func test_base64Decode_MorePaddingThanNecessaryIgnoreWhitespace() { + XCTAssertEqual(Data(base64Encoded: "", options: .ignoreUnknownCharacters), Data()) + XCTAssertNil(Data(base64Encoded: "=", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "==", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "===", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "====", options: .ignoreUnknownCharacters)) + for x in 5..<1000 { + XCTAssertNil(Data(base64Encoded: String(repeating: "=", count: x), options: .ignoreUnknownCharacters)) + } + + XCTAssertEqual(Data(base64Encoded: "AAAA", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA=", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA =", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA==", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA = =", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA===", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA = = = ", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA====", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAAA = = = =", options: .ignoreUnknownCharacters), Data([0, 0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA=", options: .ignoreUnknownCharacters), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA==", options: .ignoreUnknownCharacters), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA===", options: .ignoreUnknownCharacters), Data([0, 0])) + XCTAssertEqual(Data(base64Encoded: "AAA====", options: .ignoreUnknownCharacters), Data([0, 0])) + XCTAssertNil(Data(base64Encoded: "AA=", options: .ignoreUnknownCharacters)) + XCTAssertEqual(Data(base64Encoded: "AA==", options: .ignoreUnknownCharacters), Data([0])) + XCTAssertEqual(Data(base64Encoded: "AA===", options: .ignoreUnknownCharacters), Data([0])) + XCTAssertEqual(Data(base64Encoded: "AA====", options: .ignoreUnknownCharacters), Data([0])) + XCTAssertNil(Data(base64Encoded: "A=", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "A==", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "A===", options: .ignoreUnknownCharacters)) + XCTAssertNil(Data(base64Encoded: "A====", options: .ignoreUnknownCharacters)) + } + + func test_base64Data_medium() { let data = Data("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut at tincidunt arcu. Suspendisse nec sodales erat, sit amet imperdiet ipsum. Etiam sed ornare felis. Nunc mauris turpis, bibendum non lectus quis, malesuada placerat turpis. Nam adipiscing non massa et semper. Nulla convallis semper bibendum. Aliquam dictum nulla cursus mi ultricies, at tincidunt mi sagittis. Nulla faucibus at dui quis sodales. Morbi rutrum, dui id ultrices venenatis, arcu urna egestas felis, vel suscipit mauris arcu quis risus. Nunc venenatis ligula at orci tristique, et mattis purus pulvinar. Etiam ultricies est odio. Nunc eleifend malesuada justo, nec euismod sem ultrices quis. Etiam nec nibh sit amet lorem faucibus dapibus quis nec leo. Praesent sit amet mauris vel lacus hendrerit porta mollis consectetur mi. Donec eget tortor dui. Morbi imperdiet, arcu sit amet elementum interdum, quam nisl tempor quam, vitae feugiat augue purus sed lacus. In ac urna adipiscing purus venenatis volutpat vel et metus. Nullam nec auctor quam. Phasellus porttitor felis ac nibh gravida suscipit tempus at ante. Nunc pellentesque iaculis sapien a mattis. Aenean eleifend dolor non nunc laoreet, non dictum massa aliquam. Aenean quis turpis augue. Praesent augue lectus, mollis nec elementum eu, dignissim at velit. Ut congue neque id ullamcorper pellentesque. Maecenas euismod in elit eu vehicula. Nullam tristique dui nulla, nec convallis metus suscipit eget. Cras semper augue nec cursus blandit. Nulla rhoncus et odio quis blandit. Praesent lobortis dignissim velit ut pulvinar. Duis interdum quam adipiscing dolor semper semper. Nunc bibendum convallis dui, eget mollis magna hendrerit et. Morbi facilisis, augue eu fringilla convallis, mauris est cursus dolor, eu posuere odio nunc quis orci. Ut eu justo sem. Phasellus ut erat rhoncus, faucibus arcu vitae, vulputate erat. Aliquam nec magna viverra, interdum est vitae, rhoncus sapien. Duis tincidunt tempor ipsum ut dapibus. Nullam commodo varius metus, sed sollicitudin eros. Etiam nec odio et dui tempor blandit posuere.".utf8) let base64 = data.base64EncodedString()