diff --git a/Sources/ULID/Data+Base32.swift b/Sources/ULID/Data+Base32.swift index c9f41f5..73c06d2 100644 --- a/Sources/ULID/Data+Base32.swift +++ b/Sources/ULID/Data+Base32.swift @@ -39,103 +39,74 @@ extension Data { /// Decode Crockford's Base32 init?(base32Encoded base32String: String, using table: [UInt8] = Base32.crockfordsDecodingTable) { var base32String = base32String - while let last = base32String.last, last == "=" { - base32String.removeLast() + if base32String.last == "=", let index = base32String.lastIndex(where: { $0 != "=" }) { + base32String = String(base32String[...index]) } - let result: Data? = base32String.withCString(encodedAs: Unicode.UTF8.self) { (src) in - func _strlen(_ str: UnsafePointer) -> Int { - var str = str - var i = 0 - while str.pointee != 0 { - str += 1 - i += 1 - } - return i - } - - let srclen = _strlen(src) - guard [0, 2, 4, 5, 7].contains(srclen % 8) else { - return nil - } - - let dstlen = srclen * 5 / 8 - - var buffer = Data(count: dstlen) - let success: Bool = buffer.withUnsafeMutableBytes { (dst: UnsafeMutableRawBufferPointer) -> Bool in - var srcleft = srclen - var srcp = src - - var dsti: UnsafeMutableRawBufferPointer.Index = 0 - - let work = UnsafeMutablePointer.allocate(capacity: 8) - defer { work.deallocate() } - - while srcleft > 0 { - let worklen = Swift.min(8, srcleft) - for i in 0 ..< worklen { - work[i] = table[Int(srcp[i])] - if work[i] == 0xff { - return false - } - } - - switch worklen { - case 8: - dst[dsti + 4] = (work[6] << 5) | (work[7] ) - fallthrough - case 7: - dst[dsti + 3] = (work[4] << 7) | (work[5] << 2) | (work[6] >> 3) - fallthrough - case 5: - dst[dsti + 2] = (work[3] << 4) | (work[4] >> 1) - fallthrough - case 4: - dst[dsti + 1] = (work[1] << 6) | (work[2] << 1) | (work[3] >> 4) - fallthrough - case 2: - dst[dsti + 0] = (work[0] << 3) | (work[1] >> 2) - default: - break - } - - srcp += 8 - srcleft -= 8 - dsti += 5 - } - - return true - } - - guard success else { - return nil - } - - return buffer - } - - guard let data = result else { + let src = base32String.utf8 + guard [0, 2, 4, 5, 7].contains(src.count % 8) else { return nil } + var srcleft = src.count + var srci = 0 - self = data + let dstlen = src.count * 5 / 8 + let dst = UnsafeMutablePointer.allocate(capacity: dstlen) + var dsti = 0 + defer { dst.deallocate() } + + let work = UnsafeMutablePointer.allocate(capacity: 8) + defer { work.deallocate() } + + while srcleft > 0 { + let worklen = Swift.min(8, srcleft) + for i in 0 ..< worklen { + work[i] = table[Int(src[src.index(src.startIndex, offsetBy: srci + i)])] + if work[i] == 0xff { + return nil + } + } + + switch worklen { + case 8: + dst[dsti + 4] = (work[6] << 5) | (work[7] ) + fallthrough + case 7: + dst[dsti + 3] = (work[4] << 7) | (work[5] << 2) | (work[6] >> 3) + fallthrough + case 5: + dst[dsti + 2] = (work[3] << 4) | (work[4] >> 1) + fallthrough + case 4: + dst[dsti + 1] = (work[1] << 6) | (work[2] << 1) | (work[3] >> 4) + fallthrough + case 2: + dst[dsti + 0] = (work[0] << 3) | (work[1] >> 2) + default: + break + } + + srci += 8 + srcleft -= 8 + dsti += 5 + } + + self = Data(bytes: dst, count: dstlen) } /// Encode Crockford's Base32 func base32EncodedString(padding: Bool = true, using table: [UInt8] = Base32.crockfordsEncodingTable) -> String { - var srcleft = self.count - - let dstlen: Int - if padding { - dstlen = (self.count + 4) / 5 * 8 - } else { - dstlen = (self.count * 8 + 4) / 5 - } - var dstleft = dstlen - return self.withUnsafeBytes { (src: UnsafeRawBufferPointer) -> String in - var srci: UnsafeRawBufferPointer.Index = 0 + var srcleft = src.count + var srci = 0 + let dstlen: Int + if padding { + dstlen = (src.count + 4) / 5 * 8 + } else { + dstlen = (src.count * 8 + 4) / 5 + } + var dstleft = dstlen let dst = UnsafeMutablePointer.allocate(capacity: dstlen + 1) var dstp = dst defer { dst.deallocate() } @@ -179,18 +150,18 @@ extension Data { if padding { switch srcleft { case 1: - dstp[2] = "=".utf8.first! - dstp[3] = "=".utf8.first! + dstp[2] = 0x3d + dstp[3] = 0x3d fallthrough case 2: - dstp[4] = "=".utf8.first! + dstp[4] = 0x3d fallthrough case 3: - dstp[5] = "=".utf8.first! - dstp[6] = "=".utf8.first! + dstp[5] = 0x3d + dstp[6] = 0x3d fallthrough case 4: - dstp[7] = "=".utf8.first! + dstp[7] = 0x3d default: break } diff --git a/Sources/ULID/ULID.swift b/Sources/ULID/ULID.swift index a10d544..e128898 100644 --- a/Sources/ULID/ULID.swift +++ b/Sources/ULID/ULID.swift @@ -28,7 +28,7 @@ public struct ULID: Hashable, Equatable, Comparable, CustomStringConvertible { } public init?(ulidString string: String) { - guard string.count == 26, let data = Data(base32Encoded: "000000" + string) else { + guard string.utf8.count == 26, let data = Data(base32Encoded: "000000" + string) else { return nil } withUnsafeMutableBytes(of: &ulid) { diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 36ad902..6009b9b 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,6 +1,8 @@ import XCTest + import ULIDTests var tests = [XCTestCaseEntry]() -tests += ULIDTests.allTests() +tests += ULIDTests.__allTests() + XCTMain(tests) diff --git a/Tests/ULIDTests/Data+Base32Tests.swift b/Tests/ULIDTests/Data+Base32Tests.swift index e0828d6..f24894a 100644 --- a/Tests/ULIDTests/Data+Base32Tests.swift +++ b/Tests/ULIDTests/Data+Base32Tests.swift @@ -224,28 +224,4 @@ final class Base32Tests: XCTestCase { XCTAssertNil(data) } - // MARK: - - - static var allTests = [ - ("testEncodeBase32", testEncodeBase32), - ("testEncode1", testEncode1), - ("testEncode2", testEncode2), - ("testEncode3", testEncode3), - ("testEncode4", testEncode4), - ("testEncode5", testEncode5), - ("testEncode6", testEncode6), - ("testEncode7", testEncode7), - ("testEncode8", testEncode8), - ("testEncodePad1", testEncodePad1), - ("testEncodePad2", testEncodePad2), - ("testEncodePad3", testEncodePad3), - ("testEncodePad4", testEncodePad4), - ("testEncodeNoPad", testEncodeNoPad), - ("testDecodeBase32", testDecodeBase32), - ("testDecodeTable", testDecodeTable), - ("testDecodeInvalidCharacter", testDecodeInvalidCharacter), - ("testDecodePadding", testDecodePadding), - ("testDecodeIncorrectLength", testDecodeIncorrectLength) - ] - } diff --git a/Tests/ULIDTests/ULIDTests.swift b/Tests/ULIDTests/ULIDTests.swift index 65237c6..82449f5 100644 --- a/Tests/ULIDTests/ULIDTests.swift +++ b/Tests/ULIDTests/ULIDTests.swift @@ -61,6 +61,11 @@ final class ULIDTests: XCTestCase { XCTAssertEqual(expected, actual!.ulidString) } + func testParseULIDStringError() { + let zero = "" + XCTAssertNil(ULID(ulidString: zero)) + } + func testParseULIDData() { let expected: [UInt8] = [ 0x01, 0x68, 0x3D, 0x17, 0x73, 0x09, 0xE5, 0x2D, @@ -73,6 +78,11 @@ final class ULIDTests: XCTestCase { XCTAssertEqual(expected, Array(actual!.ulidData)) } + func testParseULIDDataError() { + let zero = Data() + XCTAssertNil(ULID(ulidData: zero)) + } + func testULIDDataLength() { let ulid = ULID() XCTAssertEqual(16, ulid.ulidData.count) @@ -127,10 +137,10 @@ final class ULIDTests: XCTestCase { } func testComparable1() { - let lhs = ULID(ulid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) - let rhs = ULID(ulid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)) - XCTAssertTrue(lhs < rhs) - XCTAssertFalse(lhs > rhs) + let lhs = ULID(ulid: (1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + let rhs = ULID(ulid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + XCTAssertFalse(lhs < rhs) + XCTAssertTrue(lhs > rhs) } func testComparable2() { @@ -141,6 +151,13 @@ final class ULIDTests: XCTestCase { } func testComparable3() { + let lhs = ULID(ulid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)) + let rhs = ULID(ulid: (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1)) + XCTAssertTrue(lhs < rhs) + XCTAssertFalse(lhs > rhs) + } + + func testComparable4() { let now = Date() let ulid0 = ULID(timestamp: now.addingTimeInterval(-120)) let ulid1 = ULID(timestamp: now.addingTimeInterval(-60)) @@ -179,6 +196,21 @@ final class ULIDTests: XCTestCase { } } + func testDecodableError() { + let json = """ + { "ulid" : "" } + """ + do { + let decoder = JSONDecoder() + _ = try decoder.decode(CodableModel.self, from: json.data(using: .utf8)!) + XCTFail() + } catch DecodingError.dataCorrupted { + // Success + } catch { + XCTFail() + } + } + func testEncodable() { let ulidString = "01D0YHEWR9WMPY4NNTPK1MR1TQ" let expected = """ @@ -198,27 +230,6 @@ final class ULIDTests: XCTestCase { XCTAssertEqual(16, MemoryLayout.size) } - static var allTests = [ - ("testGenerateTimestamp", testGenerateTimestamp), - ("testGenerateRandomness", testGenerateRandomness), - ("testParseULIDString", testParseULIDString), - ("testParseULIDData", testParseULIDData), - ("testULIDDataLength", testULIDDataLength), - ("testULIDStringLength", testULIDStringLength), - ("testHashable1", testHashable1), - ("testHashable2", testHashable2), - ("testHashable3", testHashable3), - ("testEquatable1", testEquatable1), - ("testEquatable2", testEquatable2), - ("testComparable1", testComparable1), - ("testComparable2", testComparable2), - ("testComparable3", testComparable3), - ("testCustomStringConvertible", testCustomStringConvertible), - ("testDecodable", testDecodable), - ("testEncodable", testEncodable), - ("testMemorySize", testMemorySize) - ] - } private struct MockRandomNumberGenerator: RandomNumberGenerator { diff --git a/Tests/ULIDTests/XCTestManifests.swift b/Tests/ULIDTests/XCTestManifests.swift index f7d0786..563b5b6 100644 --- a/Tests/ULIDTests/XCTestManifests.swift +++ b/Tests/ULIDTests/XCTestManifests.swift @@ -1,18 +1,67 @@ -// -// XCTestManifests.swift -// ULIDTests -// -// Created by Yasuhiro Hatta on 2019/01/11. -// Copyright © 2019 yaslab. All rights reserved. -// - +#if !canImport(ObjectiveC) import XCTest -#if !os(macOS) -public func allTests() -> [XCTestCaseEntry] { +extension Base32Tests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__Base32Tests = [ + ("testDecodeBase32", testDecodeBase32), + ("testDecodeIncorrectLength", testDecodeIncorrectLength), + ("testDecodeInvalidCharacter", testDecodeInvalidCharacter), + ("testDecodePadding", testDecodePadding), + ("testDecodeTable", testDecodeTable), + ("testEncode1", testEncode1), + ("testEncode2", testEncode2), + ("testEncode3", testEncode3), + ("testEncode4", testEncode4), + ("testEncode5", testEncode5), + ("testEncode6", testEncode6), + ("testEncode7", testEncode7), + ("testEncode8", testEncode8), + ("testEncodeBase32", testEncodeBase32), + ("testEncodeNoPad", testEncodeNoPad), + ("testEncodePad1", testEncodePad1), + ("testEncodePad2", testEncodePad2), + ("testEncodePad3", testEncodePad3), + ("testEncodePad4", testEncodePad4), + ] +} + +extension ULIDTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__ULIDTests = [ + ("testComparable1", testComparable1), + ("testComparable2", testComparable2), + ("testComparable3", testComparable3), + ("testComparable4", testComparable4), + ("testCustomStringConvertible", testCustomStringConvertible), + ("testDecodable", testDecodable), + ("testDecodableError", testDecodableError), + ("testEncodable", testEncodable), + ("testEquatable1", testEquatable1), + ("testEquatable2", testEquatable2), + ("testGenerateRandomness", testGenerateRandomness), + ("testGenerateTimestamp", testGenerateTimestamp), + ("testHashable1", testHashable1), + ("testHashable2", testHashable2), + ("testHashable3", testHashable3), + ("testMemorySize", testMemorySize), + ("testParseULIDData", testParseULIDData), + ("testParseULIDDataError", testParseULIDDataError), + ("testParseULIDString", testParseULIDString), + ("testParseULIDStringError", testParseULIDStringError), + ("testULIDDataLength", testULIDDataLength), + ("testULIDStringLength", testULIDStringLength), + ] +} + +public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(Base32Tests.allTests), - testCase(ULIDTests.allTests) + testCase(Base32Tests.__allTests__Base32Tests), + testCase(ULIDTests.__allTests__ULIDTests), ] } #endif