Skip to content

Commit

Permalink
Merge pull request #8 from brightdigit/feature/pattern-matching
Browse files Browse the repository at this point in the history
Feature/pattern matching
  • Loading branch information
leogdion authored May 20, 2020
2 parents b18045a + 1927177 commit 3546014
Show file tree
Hide file tree
Showing 11 changed files with 171 additions and 45 deletions.
7 changes: 3 additions & 4 deletions Scripts/script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ if [[ $TRAVIS_OS_NAME = 'osx' ]]; then
pod lib lint
swift package generate-xcodeproj
pod install --silent --project-directory=Example
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "iOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "tvOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "macOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO &
wait
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "iOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "tvOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
xcodebuild -quiet -workspace Example/Example.xcworkspace -scheme "macOS Example" ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO
fi
5 changes: 5 additions & 0 deletions Sources/Base32Crockford/Base32CrockfordComparer.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import Foundation

public protocol Base32CrockfordComparer {
func data(_ data: Data, hasEncodedPrefix prefix: String) -> Bool
}
94 changes: 62 additions & 32 deletions Sources/Base32Crockford/Base32CrockfordEncoding.swift
Original file line number Diff line number Diff line change
@@ -1,12 +1,61 @@
import Foundation

public struct Base32CrockfordEncoding: Base32CrockfordEncodingProtocol {
public static let encoding: Base32CrockfordEncodingProtocol = Base32CrockfordEncoding()
public struct Base32CrockfordEncoding: Base32CrockfordEncodingProtocol, Base32CrockfordComparer {
fileprivate static let _encoding = Base32CrockfordEncoding()

static let characters = "0123456789abcdefghjkmnpqrtuvwxyz".uppercased()
// static let checksum = [1, 1, 2, 4]
public static var encoding: Base32CrockfordEncodingProtocol {
return _encoding
}

public static var comparer: Base32CrockfordComparer {
return _encoding
}

fileprivate static let characters = "0123456789abcdefghjkmnpqrtuvwxyz".uppercased()

fileprivate struct ChecksumError: Error {}
fileprivate func sizeOf(checksumFrom string: String) -> Int {
let strBitCount = string.count * 5
let dataBitCount = Int(floor(Double(strBitCount) / 8)) * 8
return strBitCount - dataBitCount
}

fileprivate func decodeWithoutChecksum(base32Encoded string: String) -> Data {
let standardized = standardize(string: string)
let checksumSize = sizeOf(checksumFrom: standardized)

return decode(standardizedString: standardized, withChecksumSize: checksumSize)
}

fileprivate func verifyChecksum(_ checksumSize: Int, _ standardized: String) throws {
let lastValue: UInt8?
if checksumSize != 0 {
let lastIndex = Base32CrockfordEncoding.characters.firstIndex(of: standardized.last!)!
lastValue = UInt8(Base32CrockfordEncoding.characters.distance(from: Base32CrockfordEncoding.characters.startIndex, to: lastIndex))
} else {
lastValue = nil
}

if let lastValue = lastValue {
let checksumValue = (lastValue << (8 - checksumSize)) >> (8 - checksumSize)
guard checksumValue == 0 else {
throw ChecksumError()
}
}
}

fileprivate func decode(standardizedString standardized: String, withChecksumSize checksumSize: Int) -> Data {
let values = standardized.map { character -> String.IndexDistance in
let lastIndex = Base32CrockfordEncoding.characters.firstIndex(of: character)!
return Base32CrockfordEncoding.characters.distance(from: Base32CrockfordEncoding.characters.startIndex, to: lastIndex)
}

struct ChecksumError: Error {}
let bitString = values.map { String($0, radix: 2).pad(toSize: 5) }.joined(separator: "")

let bitStringWithoutChecksum = String(bitString[bitString.startIndex ... bitString.index(bitString.endIndex, offsetBy: -checksumSize - 1)])
let dataBytes = bitStringWithoutChecksum.split(by: 8).compactMap { UInt8($0, radix: 2) }
return Data(dataBytes)
}

public func encode(data: Data) -> String {
let dataBitCount = data.count * 8
Expand Down Expand Up @@ -39,34 +88,15 @@ public struct Base32CrockfordEncoding: Base32CrockfordEncodingProtocol {
}

public func decode(base32Encoded string: String) throws -> Data {
let standardized = string.uppercased()
let strBitCount = string.count * 5
let dataBitCount = Int(floor(Double(strBitCount) / 8)) * 8
let checksumSize = strBitCount - dataBitCount
let lastValue: UInt8?
if checksumSize != 0 {
let lastIndex = Base32CrockfordEncoding.characters.firstIndex(of: standardized.last!)!
lastValue = UInt8(Base32CrockfordEncoding.characters.distance(from: Base32CrockfordEncoding.characters.startIndex, to: lastIndex))
} else {
lastValue = nil
}

if let lastValue = lastValue {
let checksumValue = (lastValue << (8 - checksumSize)) >> (8 - checksumSize)
guard checksumValue == 0 else {
throw ChecksumError()
}
}

let values = standardized.map { character -> String.IndexDistance in
let lastIndex = Base32CrockfordEncoding.characters.firstIndex(of: character)!
return Base32CrockfordEncoding.characters.distance(from: Base32CrockfordEncoding.characters.startIndex, to: lastIndex)
}
let standardized = standardize(string: string)
let checksumSize = sizeOf(checksumFrom: standardized)
try verifyChecksum(checksumSize, standardized)

let bitString = values.map { String($0, radix: 2).pad(toSize: 5) }.joined(separator: "")
return decode(standardizedString: standardized, withChecksumSize: checksumSize)
}

let bitStringWithoutChecksum = String(bitString[bitString.startIndex ... bitString.index(bitString.endIndex, offsetBy: -checksumSize - 1)])
let dataBytes = bitStringWithoutChecksum.split(by: 8).compactMap { UInt8($0, radix: 2) }
return Data(dataBytes)
public func data(_ data: Data, hasEncodedPrefix prefix: String) -> Bool {
let prefixData = decodeWithoutChecksum(base32Encoded: prefix)
return zip(data, prefixData).allSatisfy { $0 == $1 }
}
}
10 changes: 10 additions & 0 deletions Sources/Base32Crockford/Base32CrockfordEncodingProtocol.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,13 @@ public protocol Base32CrockfordEncodingProtocol: Base32CrockfordGenerator {
func decode(base32Encoded string: String) throws -> Data
static var encoding: Base32CrockfordEncodingProtocol { get }
}

public extension Base32CrockfordEncodingProtocol {
func standardize(string: String) -> String {
return string
.uppercased()
.replacingOccurrences(of: "O", with: "0")
.replacingOccurrences(of: "I", with: "1")
.replacingOccurrences(of: "L", with: "1")
}
}
32 changes: 32 additions & 0 deletions Tests/Base32CrockfordTests/Base32PatternTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@testable import Base32Crockford
import XCTest

final class Base32EqualityTests: XCTestCase {
func testExample() {
let values = ["0": "O", "1": "I", "I": "L"]
let encoding = Base32CrockfordEncoding()
var checks = 0
for _ in 0 ... 2000 {
let id = UUID()
let data = Data(Array(uuid: id))
let fullId = encoding.encode(data: data)
let shortId = String(fullId[fullId.startIndex ... fullId.index(fullId.startIndex, offsetBy: 4)])
var shortValues = values.reduce([shortId]) { (shortValues, arg1) -> [String] in

let (key, value) = arg1
let current = shortValues.last ?? shortId

return shortValues + [current.replacingOccurrences(of: key, with: value)]
}
shortValues.append((shortValues.last ?? shortId).lowercased())
shortValues = [String](Set(shortValues))
for aShortId in shortValues {
XCTAssert(Base32CrockfordEncoding.comparer.data(data, hasEncodedPrefix: aShortId))
checks += 1
}
if checks > 500 {
return
}
}
}
}
29 changes: 21 additions & 8 deletions Tests/Base32CrockfordTests/EncodeDecodeTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,20 +31,33 @@ final class EncodeDecodeTests: XCTestCase {
}
}

func decode(value: String, withExpected expected: Data) {
let actual: Data
do {
actual = try Base32CrockfordEncoding.encoding.decode(base32Encoded: value)
} catch {
XCTFail(error.localizedDescription)
return
}
XCTAssertEqual(actual, expected)
}

func testDecoding() {
for (expectedString, value) in data {
let actual: Data
guard let expected = expectedString.data(using: .utf8) else {
XCTFail("Unable to create data from string")
continue
}
do {
actual = try Base32CrockfordEncoding.encoding.decode(base32Encoded: value)
} catch {
XCTFail(error.localizedDescription)
continue
}
XCTAssertEqual(actual, expected)
var newValue = value
decode(value: value, withExpected: expected)
newValue = newValue.replacingOccurrences(of: "0", with: "O")
decode(value: newValue, withExpected: expected)
newValue = newValue.replacingOccurrences(of: "1", with: "L")
decode(value: newValue, withExpected: expected)
newValue = newValue.replacingOccurrences(of: "L", with: "I")
decode(value: newValue, withExpected: expected)
newValue = newValue.lowercased()
decode(value: newValue, withExpected: expected)
}
}
}
10 changes: 10 additions & 0 deletions Tests/Base32CrockfordTests/XCTestManifests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,15 @@
]
}

extension Base32EqualityTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
// to regenerate.
static let __allTests__Base32EqualityTests = [
("testExample", testExample)
]
}

extension EncodeDecodeTests {
// DO NOT MODIFY: This is autogenerated, use:
// `swift test --generate-linuxmain`
Expand All @@ -39,6 +48,7 @@
return [
testCase(ArrayTests.__allTests__ArrayTests),
testCase(Base32CrockfordTests.__allTests__Base32CrockfordTests),
testCase(Base32EqualityTests.__allTests__Base32EqualityTests),
testCase(EncodeDecodeTests.__allTests__EncodeDecodeTests)
]
}
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
## Protocols

- [Base32CrockfordComparer](protocols/Base32CrockfordComparer.md)
- [Base32CrockfordEncodingProtocol](protocols/Base32CrockfordEncodingProtocol.md)
- [Base32CrockfordGenerator](protocols/Base32CrockfordGenerator.md)

Expand Down
6 changes: 6 additions & 0 deletions docs/extensions/Base32CrockfordEncodingProtocol.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@
# `Base32CrockfordEncodingProtocol`

## Methods
### `standardize(string:)`

```swift
func standardize(string: String) -> String
```

### `generateIdentifier(from:)`

```swift
Expand Down
14 changes: 14 additions & 0 deletions docs/protocols/Base32CrockfordComparer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
**PROTOCOL**

# `Base32CrockfordComparer`

```swift
public protocol Base32CrockfordComparer
```

## Methods
### `data(_:hasEncodedPrefix:)`

```swift
func data(_ data: Data, hasEncodedPrefix prefix: String) -> Bool
```
8 changes: 7 additions & 1 deletion docs/structs/Base32CrockfordEncoding.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# `Base32CrockfordEncoding`

```swift
public struct Base32CrockfordEncoding: Base32CrockfordEncodingProtocol
public struct Base32CrockfordEncoding: Base32CrockfordEncodingProtocol, Base32CrockfordComparer
```

## Methods
Expand All @@ -18,3 +18,9 @@ public func encode(data: Data) -> String
```swift
public func decode(base32Encoded string: String) throws -> Data
```

### `data(_:hasEncodedPrefix:)`

```swift
public func data(_ data: Data, hasEncodedPrefix prefix: String) -> Bool
```

0 comments on commit 3546014

Please sign in to comment.