From 72f2eb36104166c6d23d73c503c5f00ca15b3bb5 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 3 Oct 2024 20:13:43 +0200 Subject: [PATCH 1/7] Make decoding go fast! --- .../Data/Data+Base64.swift | 702 ++++++++++++++---- .../JSON/JSONDecoder.swift | 2 +- 2 files changed, 554 insertions(+), 150 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index ac23101e0..4781cac1a 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -10,6 +10,21 @@ // //===----------------------------------------------------------------------===// +#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 + private enum Base64Error: Error { case invalidElementCount case cannotDecode @@ -26,21 +41,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 +54,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 +77,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 +217,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 +418,536 @@ extension Base64 { } } } + +// MARK: - Decoding - + +extension Base64 { + + struct DecodingError: Error, Equatable { + fileprivate enum _Internal: Error, Equatable { + case invalidLength + case invalidCharacter(UInt8) + case unexpectedPaddingCharacter + case unexpectedEnd + } + + fileprivate let value: _Internal + fileprivate init(_ value: _Internal) { + self.value = value + } + + static var invalidLength: Self { .init(.invalidLength) } + static func invalidCharacter(_ character: UInt8) -> Self { .init(.invalidCharacter(character)) } + static var unexpectedPaddingCharacter: Self { .init(.unexpectedPaddingCharacter) } + static var unexpectedEnd: Self { .init(.unexpectedEnd) } + } + + static func decode(string encoded: String, options: Data.Base64DecodingOptions = []) throws -> Data { + let decoded = try encoded.utf8.withContiguousStorageIfAvailable { characterPointer -> Data in + guard characterPointer.count > 0 else { + return Data() + } + + let outputLength = ((characterPointer.count + 3) / 4) * 3 + + return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in + let pointer = malloc(outputLength) + let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) + let target = UnsafeMutableBufferPointer(start: other, count: outputLength) + var length = outputLength + try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } + } + + if decoded != nil { + return decoded! + } + + var encoded = encoded + encoded.makeContiguousUTF8() + return try Self.decode(string: encoded, options: options) + } + + static func decode(data encoded: Data, options: Data.Base64DecodingOptions = []) throws -> Data { + let decoded = try encoded.withContiguousStorageIfAvailable { characterPointer -> Data in + // `withContiguousStorageIfAvailable` sadly does not support typed throws + guard characterPointer.count > 0 else { + return Data() + } + + let outputLength = ((characterPointer.count + 3) / 4) * 3 + + return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in + let pointer = malloc(outputLength) + let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) + let target = UnsafeMutableBufferPointer(start: other, count: outputLength) + var length = outputLength + try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } + } + + if decoded != nil { + return decoded! + } + + return try Self.decode(bytes: Array(encoded), options: options) + } + + static func decode(bytes: Buffer, options: Data.Base64DecodingOptions = []) throws -> Data where Buffer.Element == UInt8 { + guard bytes.count > 0 else { + return Data() + } + + let decoded = try bytes.withContiguousStorageIfAvailable { characterPointer -> Data in + // `withContiguousStorageIfAvailable` sadly does not support typed throws + guard characterPointer.count > 0 else { + return Data() + } + + let outputLength = ((characterPointer.count + 3) / 4) * 3 + + return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in + let pointer = malloc(outputLength) + let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) + let target = UnsafeMutableBufferPointer(start: other, count: outputLength) + var length = outputLength + try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } + } + + if decoded != nil { + return decoded! + } + + return try self.decode(bytes: Array(bytes), options: options) + } + + static func _decodeChromium( + from inBuffer: UnsafeBufferPointer, + into outBuffer: UnsafeMutableBufferPointer, + length: inout Int, + options: Data.Base64DecodingOptions = [] + ) throws(DecodingError) { + let remaining = inBuffer.count % 4 + guard remaining == 0 else { throw DecodingError.invalidLength } + + let outputLength = ((inBuffer.count + 3) / 4) * 3 + let fullchunks = remaining == 0 ? inBuffer.count / 4 - 1 : inBuffer.count / 4 + 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 _decodeChromiumIgnoringErrors( + from inBuffer: UnsafeBufferPointer, + into outBuffer: UnsafeMutableBufferPointer, + length: inout Int, + options: Data.Base64DecodingOptions + ) throws(DecodingError) { + let remaining = inBuffer.count % 4 + if !options.contains(.ignoreUnknownCharacters) { + guard remaining == 0 else { throw DecodingError.invalidLength } + } + + 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 + + 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 { + break // the loop + } + + guard options.contains(.ignoreUnknownCharacters) else { + // TODO: Inspect characters here better + throw DecodingError.invalidCharacter(inBuffer[inIndex]) + } + + // 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 + } + } + fatalError() + } + + 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 + } + + // TODO: check we have at least two more characters, or they are all bs + + 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 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) + + return try decoding0.withUnsafeBufferPointer { d0 throws(E) -> R in + try decoding1.withUnsafeBufferPointer { d1 throws(E) -> R in + try decoding2.withUnsafeBufferPointer { d2 throws(E) -> R in + try decoding3.withUnsafeBufferPointer { d3 throws(E) -> R in + try body(d0, d1, d2, d3) + } + } + } + } + } + + 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 8501e8568..35ca69078 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 { From 0ffe913c762965fa61e0f31ebb079f1853d5e8cd Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Wed, 5 Feb 2025 12:16:45 +0100 Subject: [PATCH 2/7] Fixes --- .../Data/Data+Base64.swift | 77 +++++++------------ 1 file changed, 29 insertions(+), 48 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 4781cac1a..925b96468 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -444,21 +444,8 @@ extension Base64 { static func decode(string encoded: String, options: Data.Base64DecodingOptions = []) throws -> Data { let decoded = try encoded.utf8.withContiguousStorageIfAvailable { characterPointer -> Data in - guard characterPointer.count > 0 else { - return Data() - } - - let outputLength = ((characterPointer.count + 3) / 4) * 3 - - return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in - let pointer = malloc(outputLength) - let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) - let target = UnsafeMutableBufferPointer(start: other, count: outputLength) - var length = outputLength - try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) - - return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) - } + // `withContiguousStorageIfAvailable` sadly does not support typed throws + try Self._decodeToData(from: characterPointer, options: options) } if decoded != nil { @@ -471,23 +458,9 @@ extension Base64 { } static func decode(data encoded: Data, options: Data.Base64DecodingOptions = []) throws -> Data { - let decoded = try encoded.withContiguousStorageIfAvailable { characterPointer -> Data in + let decoded = try encoded.withContiguousStorageIfAvailable { bufferPointer -> Data in // `withContiguousStorageIfAvailable` sadly does not support typed throws - guard characterPointer.count > 0 else { - return Data() - } - - let outputLength = ((characterPointer.count + 3) / 4) * 3 - - return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in - let pointer = malloc(outputLength) - let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) - let target = UnsafeMutableBufferPointer(start: other, count: outputLength) - var length = outputLength - try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) - - return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) - } + try Self._decodeToData(from: bufferPointer, options: options) } if decoded != nil { @@ -502,23 +475,9 @@ extension Base64 { return Data() } - let decoded = try bytes.withContiguousStorageIfAvailable { characterPointer -> Data in + let decoded = try bytes.withContiguousStorageIfAvailable { bufferPointer -> Data in // `withContiguousStorageIfAvailable` sadly does not support typed throws - guard characterPointer.count > 0 else { - return Data() - } - - let outputLength = ((characterPointer.count + 3) / 4) * 3 - - return try characterPointer.withMemoryRebound(to: UInt8.self) { input -> Data in - let pointer = malloc(outputLength) - let other = pointer?.bindMemory(to: UInt8.self, capacity: outputLength) - let target = UnsafeMutableBufferPointer(start: other, count: outputLength) - var length = outputLength - try Self._decodeChromiumIgnoringErrors(from: input, into: target, length: &length, options: options) - - return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) - } + try Self._decodeToData(from: bufferPointer, options: options) } if decoded != nil { @@ -528,11 +487,33 @@ extension Base64 { 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._decodeChromiumIgnoringErrors(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._decodeChromium(from: inBuffer, into: target, length: &length, options: options) + } + + return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) + } + static func _decodeChromium( from inBuffer: UnsafeBufferPointer, into outBuffer: UnsafeMutableBufferPointer, length: inout Int, - options: Data.Base64DecodingOptions = [] + options: Data.Base64DecodingOptions ) throws(DecodingError) { let remaining = inBuffer.count % 4 guard remaining == 0 else { throw DecodingError.invalidLength } From c310739420377b12d4b94ad8222e57d797160cb3 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Wed, 5 Feb 2025 21:48:10 +0100 Subject: [PATCH 3/7] Use typed throws everywhere to prevent allocation in the unhappy path. --- .../Data/Data+Base64.swift | 45 +++++++++++-------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 925b96468..6d94f642c 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -442,14 +442,17 @@ extension Base64 { static var unexpectedEnd: Self { .init(.unexpectedEnd) } } - static func decode(string encoded: String, options: Data.Base64DecodingOptions = []) throws -> Data { - let decoded = try encoded.utf8.withContiguousStorageIfAvailable { characterPointer -> Data in - // `withContiguousStorageIfAvailable` sadly does not support typed throws - try Self._decodeToData(from: characterPointer, options: options) + 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 decoded != nil { - return decoded! + if let result { + return try result.get() } var encoded = encoded @@ -457,31 +460,37 @@ extension Base64 { return try Self.decode(string: encoded, options: options) } - static func decode(data encoded: Data, options: Data.Base64DecodingOptions = []) throws -> Data { - let decoded = try encoded.withContiguousStorageIfAvailable { bufferPointer -> Data in - // `withContiguousStorageIfAvailable` sadly does not support typed throws - try Self._decodeToData(from: bufferPointer, 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 decoded != nil { - return decoded! + 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 -> Data where Buffer.Element == UInt8 { + static func decode(bytes: Buffer, options: Data.Base64DecodingOptions = []) throws(DecodingError) -> Data where Buffer.Element == UInt8 { guard bytes.count > 0 else { return Data() } - let decoded = try bytes.withContiguousStorageIfAvailable { bufferPointer -> Data in - // `withContiguousStorageIfAvailable` sadly does not support typed throws - try Self._decodeToData(from: bufferPointer, options: options) + 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 decoded != nil { - return decoded! + if let result { + return try result.get() } return try self.decode(bytes: Array(bytes), options: options) From e9885ff395d33e7c3f5983d2ad60ec7bd6a80622 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 20 Feb 2025 19:20:08 +0100 Subject: [PATCH 4/7] cleanup --- .../Data/Data+Base64.swift | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 6d94f642c..0f2e75137 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -423,23 +423,11 @@ extension Base64 { extension Base64 { - struct DecodingError: Error, Equatable { - fileprivate enum _Internal: Error, Equatable { - case invalidLength - case invalidCharacter(UInt8) - case unexpectedPaddingCharacter - case unexpectedEnd - } - - fileprivate let value: _Internal - fileprivate init(_ value: _Internal) { - self.value = value - } - - static var invalidLength: Self { .init(.invalidLength) } - static func invalidCharacter(_ character: UInt8) -> Self { .init(.invalidCharacter(character)) } - static var unexpectedPaddingCharacter: Self { .init(.unexpectedPaddingCharacter) } - static var unexpectedEnd: Self { .init(.unexpectedEnd) } + 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 { @@ -726,15 +714,20 @@ extension Base64 { assert(decoding2.count == 256) assert(decoding3.count == 256) - return try decoding0.withUnsafeBufferPointer { d0 throws(E) -> R in - try decoding1.withUnsafeBufferPointer { d1 throws(E) -> R in - try decoding2.withUnsafeBufferPointer { d2 throws(E) -> R in - try decoding3.withUnsafeBufferPointer { d3 throws(E) -> R in - try body(d0, d1, d2, d3) + // 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 { From 7504b37dd387d4daca280e0ed472633b49a60194 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 20 Feb 2025 19:23:39 +0100 Subject: [PATCH 5/7] More cleanup --- Sources/FoundationEssentials/Data/Data+Base64.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 0f2e75137..67c692cae 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -25,11 +25,6 @@ import WinSDK import WASILibc #endif -private enum Base64Error: Error { - case invalidElementCount - case cannotDecode -} - @available(macOS 10.10, iOS 8.0, watchOS 2.0, tvOS 9.0, *) extension Data { From 466ff253ea9f76fd301d85fd9bdb32b0549ad464 Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Thu, 20 Feb 2025 22:03:11 +0100 Subject: [PATCH 6/7] Fix tests --- .../Data/Data+Base64.swift | 140 +++++++++++++++--- .../FoundationEssentialsTests/DataTests.swift | 90 +++++++++++ 2 files changed, 207 insertions(+), 23 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 67c692cae..49c3077c3 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -587,10 +587,7 @@ extension Base64 { length: inout Int, options: Data.Base64DecodingOptions ) throws(DecodingError) { - let remaining = inBuffer.count % 4 - if !options.contains(.ignoreUnknownCharacters) { - guard remaining == 0 else { throw DecodingError.invalidLength } - } + assert(options.contains(.ignoreUnknownCharacters)) let outputLength = ((inBuffer.count + 3) / 4) * 3 guard outBuffer.count >= outputLength else { @@ -601,7 +598,7 @@ extension Base64 { var outIndex = 0 var inIndex = 0 - while inIndex + 3 < inBuffer.count { + fastLoop: while inIndex + 3 < inBuffer.count { let a0 = inBuffer[inIndex] let a1 = inBuffer[inIndex &+ 1] let a2 = inBuffer[inIndex &+ 2] @@ -613,11 +610,6 @@ extension Base64 { break // the loop } - guard options.contains(.ignoreUnknownCharacters) else { - // TODO: Inspect characters here better - throw DecodingError.invalidCharacter(inBuffer[inIndex]) - } - // 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) { @@ -639,10 +631,45 @@ extension Base64 { continue } } - fatalError() - } - inIndex &+= 4 + // 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 @@ -660,34 +687,101 @@ extension Base64 { return } - // TODO: check we have at least two more characters, or they are all bs + guard inIndex + 3 < inBuffer.count else { + if options.contains(.ignoreUnknownCharacters) { + // ensure that all remaining characters are unknown + while inIndex < inBuffer.count { + let value = inBuffer[inIndex] + if self.isValidBase64Byte(value, options: options) || value == Self.encodePaddingCharacter { + throw DecodingError.invalidCharacter(inBuffer[inIndex]) + } + inIndex &+= 1 + } + length = outIndex + return + } + throw DecodingError.invalidLength + } 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 { + 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 inIndex + 3 < inBuffer.count, inBuffer[inIndex + 3] != Self.encodePaddingCharacter { + 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 ?? 65)] | d3[Int(a3 ?? 65)] + 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]) + 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 { + guard b0 != nil, b1 != nil else { + throw DecodingError.invalidLength + } + 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 a2 != nil { + if !padding2 { outBuffer[outIndex] = newPtr[1] outIndex += 1 } - if a3 != nil { + if !padding3 { outBuffer[outIndex] = newPtr[2] outIndex += 1 } diff --git a/Tests/FoundationEssentialsTests/DataTests.swift b/Tests/FoundationEssentialsTests/DataTests.swift index 30f8bc62e..4f6d02036 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() { From 9c8f7a3c4bd9c5a420f4aec083918ff0dc5ffb1e Mon Sep 17 00:00:00 2001 From: Fabian Fett Date: Tue, 25 Feb 2025 10:27:20 +0100 Subject: [PATCH 7/7] Removed Chromium callout in function name --- Sources/FoundationEssentials/Data/Data+Base64.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/FoundationEssentials/Data/Data+Base64.swift b/Sources/FoundationEssentials/Data/Data+Base64.swift index 49c3077c3..e497426ca 100644 --- a/Sources/FoundationEssentials/Data/Data+Base64.swift +++ b/Sources/FoundationEssentials/Data/Data+Base64.swift @@ -491,17 +491,17 @@ extension Base64 { let target = UnsafeMutableBufferPointer(start: other, count: outputLength) var length = outputLength if options.contains(.ignoreUnknownCharacters) { - try Self._decodeChromiumIgnoringErrors(from: inBuffer, into: target, length: &length, options: options) + 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._decodeChromium(from: inBuffer, into: target, length: &length, options: options) + try self._decode(from: inBuffer, into: target, length: &length, options: options) } return Data(bytesNoCopy: pointer!, count: length, deallocator: .free) } - static func _decodeChromium( + static func _decode( from inBuffer: UnsafeBufferPointer, into outBuffer: UnsafeMutableBufferPointer, length: inout Int, @@ -581,7 +581,7 @@ extension Base64 { } } - static func _decodeChromiumIgnoringErrors( + static func _decodeIgnoringErrors( from inBuffer: UnsafeBufferPointer, into outBuffer: UnsafeMutableBufferPointer, length: inout Int,