master d15668564829 cached
22 files
152.3 KB
33.2k tokens
1 requests
Download .txt
Repository: jordanebelanger/SwiftyBluetooth
Branch: master
Commit: d15668564829
Files: 22
Total size: 152.3 KB

Directory structure:
gitextract_tl8xnbwt/

├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   ├── CBExtensions.swift
│   ├── CBUUIDConvertible.swift
│   ├── CBUUIDPath.swift
│   ├── Central.swift
│   ├── CentralProxy.swift
│   ├── DescriptorValue.swift
│   ├── Info.plist
│   ├── Peripheral.swift
│   ├── PeripheralProxy.swift
│   ├── SBError.swift
│   ├── SwiftyBluetooth.h
│   ├── SwiftyBluetooth.swift
│   └── Util.swift
├── SwiftyBluetooth.podspec
└── SwiftyBluetooth.xcodeproj/
    ├── project.pbxproj
    ├── project.xcworkspace/
    │   ├── contents.xcworkspacedata
    │   └── xcshareddata/
    │       └── IDEWorkspaceChecks.plist
    └── xcshareddata/
        └── xcschemes/
            └── SwiftyBluetooth.xcscheme

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore

## Build generated
build/
DerivedData/

## Various settings
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/

## Other
*.moved-aside
*.xcuserstate

## Obj-C/Swift specific
*.hmap
*.ipa
*.dSYM.zip
*.dSYM

## Playgrounds
timeline.xctimeline
playground.xcworkspace

# Swift Package Manager
#
# Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
# Packages/
.build/
.swiftpm/

# CocoaPods
#
# We recommend against adding the Pods directory to your .gitignore. However
# you should judge for yourself, the pros and cons are mentioned at:
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
#
# Pods/

# Carthage
#
# Add this line if you want to avoid checking in source code from Carthage dependencies.
# Carthage/Checkouts

Carthage/Build

# fastlane
#
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
# screenshots whenever they are needed.
# For more information about the recommended setup visit:
# https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md

fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots
fastlane/test_output

.DS_Store


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 Jordane Belanger

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Package.swift
================================================
// swift-tools-version:5.5
import PackageDescription

let package = Package(
    name: "SwiftyBluetooth",
    platforms: [
        .iOS(.v10),
        .macOS(.v10_13),
        .tvOS(.v10)
    ],
    products: [
        .library(name: "SwiftyBluetooth", targets: ["SwiftyBluetooth"])
    ],
    targets: [
        .target(name: "SwiftyBluetooth", path: "Sources", exclude: ["Info.plist"])
    ]
)


================================================
FILE: README.md
================================================
# SwiftyBluetooth
Closures based APIs for CoreBluetooth.

## Features
- Replace the delegate based interface with a closure based interface for every `CBCentralManager` and `CBPeripheral` operation.
- Notification based event for CBCentralManager state changes and state restoration.
- Notification based event for CBPeripheral name updates, characteristic value updates and services updates.
- Precise errors and guaranteed timeout for every Bluetooth operation.
- Will automatically connect to a CBPeripheral and attempt to discover the required BLE services and characteristics required for a read or write operation if necessary.

## Usage
The Library has 2 important class:  

- The `Central` class, a Singleton wrapper around `CBCentralManager` used to scan for peripherals with a closure callback and restore previous sessions.
- The `Peripheral` class, a wrapper around `CBPeripheral` used to call `CBPeripheral` functions with closure callbacks. 

Below are a couple examples of operations that might be of interest to you.

### Scanning for Peripherals
Scan for peripherals by calling `scanWithTimeout(...)` while passing a `timeout` in seconds and a `callback` closure to receive `Peripheral` result callbacks as well as update on the status of your scan:
```swift
// You can pass in nil if you want to discover all Peripherals
SwiftyBluetooth.scanForPeripherals(withServiceUUIDs: nil, timeoutAfter: 15) { scanResult in
    switch scanResult {
        case .scanStarted:
            // The scan started meaning CBCentralManager scanForPeripherals(...) was called 
        case .scanResult(let peripheral, let advertisementData, let RSSI):
            // A peripheral was found, your closure may be called multiple time with a .ScanResult enum case.
            // You can save that peripheral for future use, or call some of its functions directly in this closure.
        case .scanStopped(let error):
            // The scan stopped, an error is passed if the scan stopped unexpectedly
    }
}        
```
Note that the callback closure can be called multiple times, but always start and finish with a callback containing a `.scanStarted` and `.scanStopped` result respectively. Your callback will be called with a `.scanResult` for every unique peripheral found during the scan.  

### Connecting to a peripheral
```swift
peripheral.connect { result in 
    switch result {
    case .success:
        break // You are now connected to the peripheral
    case .failure(let error):
        break // An error happened while connecting
    }
}
```
### Reading from a peripheral's service's characteristic
If you already know the characteristic and service UUIDs you want to read from, once a peripheral has been found you can read from it right away like this: 

```swift
peripheral.readValue(ofCharacWithUUID: "2A29", fromServiceWithUUID: "180A") { result in
    switch result {
    case .success(let data):
        break // The data was read and is returned as an NSData instance
    case .failure(let error):
        break // An error happened while attempting to read the data
    }
}
```
This will connect to the peripheral if necessary and ensure that the characteristic and service needed are discovered before reading from the characteristic matching `characteristicUUID`. If the charac/service cannot be retrieved you will receive an error specifying which charac/service could not be found.

If you have a reference to a `CBCharacteristic`, you can read using the characteristic directly:
```swift
peripheral.readValue(ofCharac: charac) { result in
    switch result {
    case .success(let data):
        break // The data was read and is returned as a Data instance
    case .failure(let error):
        break // An error happened while attempting to read the data
    }
}
```
### Writing to a Peripheral's service's characteristic
If you already know the characteristic and service UUID you want to write to, once a peripheral has been found, you can write to that characteristic right away like this: 
```swift
let data = String(0b1010).dataUsingEncoding(NSUTF8StringEncoding)!
peripheral.writeValue(ofCharacWithUUID: "1d5bc11d-e28c-4157-a7be-d8b742a013d8", 
                      fromServiceWithUUID: "4011e369-5981-4dae-b686-619dc656c7ba", 
                      value: data) { result in
    switch result {
    case .success:
        break // The write was succesful.
    case .failure(let error):
        break // An error happened while writting the data.
    }
}
```
### Receiving Characteristic update notifications
Receiving characteristic value updates is done through notifications on the default `NotificationCenter`. All supported `Peripheral` notifications are part of the `PeripheralEvent` enum. Use this enum's raw values as the notification string when registering for notifications:
```swift
// First we prepare ourselves to receive update notifications 
let peripheral = somePeripheral

NotificationCenter.default.addObserver(forName: Peripheral.PeripheralCharacteristicValueUpdate, 
                                        object: peripheral, 
                                        queue: nil) { (notification) in
    let charac = notification.userInfo!["characteristic"] as! CBCharacteristic
    if let error = notification.userInfo?["error"] as? SBError {
        // Deal with error
    }
}

// We can then set a characteristic's notification value to true and start receiving updates to that characteristic
peripheral.setNotifyValue(toEnabled: true, forCharacWithUUID: "2A29", ofServiceWithUUID: "180A") { (isNotifying, error) in
    // If there were no errors, you will now receive Notifications when that characteristic value gets updated.
}
```
### Discovering services 
Discover services using the `discoverServices(...)` function:
```swift
peripheral.discoverServices(withUUIDs: nil) { result in
    switch result {
    case .success(let services):
        break // An array containing all the services requested
    case .failure(let error):
        break // A connection error or an array containing the UUIDs of the services that we're not found
    }
}
```
Like the CBPeripheral discoverServices(...) function, passing nil instead of an array of service UUIDs will discover all of this Peripheral's services.
### Discovering characteristics
Discover characteristics using the `discoverCharacteristics(...)` function. If the service on which you are attempting to discover characteristics from has not been discovered, an attempt will first be made to discover that service for you:
```swift
peripheral.discoverCharacteristics(withUUIDs: nil, ofServiceWithUUID: "180A") { result in
    // The characteristics discovered or an error if something went wrong.
    switch result {
    case .success(let services):
        break // An array containing all the characs requested.
    case .failure(let error):
        break // A connection error or an array containing the UUIDs of the charac/services that we're not found.
    }
}
```
Like the CBPeripheral discoverCharacteristics(...) function, passing nil instead of an array of service UUIDs will discover all of this service's characteristics.  
### State preservation
SwiftyBluetooth is backed by a CBCentralManager singleton wrapper and does not give you direct access to the underlying CBCentralManager. 

But, you can still setup the underlying CBCentralManager for state restoration by calling `setSharedCentralInstanceWith(restoreIdentifier: )` and use the restoreIdentifier of your choice.

Take note that this method can only be called once and must be called before anything else in the library otherwise the Central sharedInstance will be lazily initiated the first time you access it.

As such, it is recommended to call it in your App Delegate's `didFinishLaunchingWithOptions(:)`
```swift
SwiftyBluetooth.setSharedCentralInstanceWith(restoreIdentifier: "MY_APP_BLUETOOTH_STATE_RESTORE_IDENTIFIER")
```

Register for state preservation notifications on the default NotificationCenter. Those notifications will contain an array of restored `Peripheral`.
```swift
NotificationCenter.default.addObserver(forName: Central.CentralManagerWillRestoreStateNotification,
                                        object: Central.sharedInstance, 
                                         queue: nil) { (notification) in
    if let restoredPeripherals = notification.userInfo?["peripherals"] as? [Peripheral] {

    }
}
```
## Installation


### CocoaPods
Add this to your Podfile:

```ruby
pod 'SwiftyBluetooth', '~> 3.1.0'
```
### Swift Package Manager
Simply add the library to your xcode project as a "Package Dependency"

### Carthage

Add this to your Cartfile 

```ogdl
github "jordanebelanger/SwiftyBluetooth"
```

## Requirements
SwiftyBluetooth requires iOS 10.0+

## License
SwiftyBluetooth is released under the MIT License.


================================================
FILE: Sources/CBExtensions.swift
================================================
//
//  CBExtensions.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

extension CBPeripheral {
    public func serviceWithUUID(_ uuid: CBUUID) -> CBService? {
        guard let services = self.services else {
            return nil
        }
        
        return services.filter { $0.uuid == uuid }.first
    }
    
    public func servicesWithUUIDs(_ servicesUUIDs: [CBUUID]) -> (foundServices: [CBService], missingServicesUUIDs: [CBUUID]) {
        assert(servicesUUIDs.count > 0)
        
        guard let currentServices = self.services , currentServices.count > 0 else {
            return (foundServices: [], missingServicesUUIDs: servicesUUIDs)
        }
        
        let currentServicesUUIDs = currentServices.map { $0.uuid }
        
        let currentServicesUUIDsSet = Set(currentServicesUUIDs)
        let requestedServicesUUIDsSet = Set(servicesUUIDs)
        
        let foundServicesUUIDsSet = requestedServicesUUIDsSet.intersection(currentServicesUUIDsSet)
        let missingServicesUUIDsSet = requestedServicesUUIDsSet.subtracting(currentServicesUUIDsSet)
        
        let foundServices = currentServices.filter { foundServicesUUIDsSet.contains($0.uuid) }
        
        return (foundServices: foundServices, missingServicesUUIDs: Array(missingServicesUUIDsSet))
    }
    
}

extension CBService {
    public func characteristicWithUUID(_ uuid: CBUUID) -> CBCharacteristic? {
        guard let characteristics = self.characteristics else {
            return nil
        }
        
        return characteristics.filter { $0.uuid == uuid }.first
    }
    
    public func characteristicsWithUUIDs(_ characteristicsUUIDs: [CBUUID]) -> (foundCharacteristics: [CBCharacteristic], missingCharacteristicsUUIDs: [CBUUID]) {
        assert(characteristicsUUIDs.count > 0)
        
        guard let currentCharacteristics = self.characteristics , currentCharacteristics.count > 0 else {
            return (foundCharacteristics: [], missingCharacteristicsUUIDs: characteristicsUUIDs)
        }
        
        let currentCharacteristicsUUID = currentCharacteristics.map { $0.uuid }
        
        let currentCharacteristicsUUIDSet = Set(currentCharacteristicsUUID)
        let requestedCharacteristicsUUIDSet = Set(characteristicsUUIDs)
        
        let foundCharacteristicsUUIDSet = requestedCharacteristicsUUIDSet.intersection(currentCharacteristicsUUIDSet)
        let missingCharacteristicsUUIDSet = requestedCharacteristicsUUIDSet.subtracting(currentCharacteristicsUUIDSet)
        
        let foundCharacteristics = currentCharacteristics.filter { foundCharacteristicsUUIDSet.contains($0.uuid) }
        
        return (foundCharacteristics: foundCharacteristics, missingCharacteristicsUUIDs: Array(missingCharacteristicsUUIDSet))
    }
}

extension CBCharacteristic {
    public func descriptorWithUUID(_ uuid: CBUUID) -> CBDescriptor? {
        guard let descriptors = self.descriptors else {
            return nil
        }
        
        return descriptors.filter { $0.uuid == uuid }.first
    }
    
    public func descriptorsWithUUIDs(_ descriptorsUUIDs: [CBUUID]) -> (foundDescriptors: [CBDescriptor], missingDescriptorsUUIDs: [CBUUID]) {
        assert(descriptorsUUIDs.count > 0)
        
        guard let currentDescriptors = self.descriptors , currentDescriptors.count > 0 else {
            return (foundDescriptors: [], missingDescriptorsUUIDs: descriptorsUUIDs)
        }
        
        let currentDescriptorsUUIDs = currentDescriptors.map { $0.uuid }
        
        let currentDescriptorsUUIDsSet = Set(currentDescriptorsUUIDs)
        let requestedDescriptorsUUIDsSet = Set(descriptorsUUIDs)
        
        let foundDescriptorsUUIDsSet = requestedDescriptorsUUIDsSet.intersection(currentDescriptorsUUIDsSet)
        let missingDescriptorsUUIDsSet = requestedDescriptorsUUIDsSet.subtracting(currentDescriptorsUUIDsSet)
        
        let foundDescriptors = currentDescriptors.filter { foundDescriptorsUUIDsSet.contains($0.uuid) }
        
        return (foundDescriptors: foundDescriptors, missingDescriptorsUUIDs: Array(missingDescriptorsUUIDsSet))
    }
}


================================================
FILE: Sources/CBUUIDConvertible.swift
================================================
//
//  CBUUIDConvertible.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

/// Instead of directly using CBUUIDs in the Central and Peripheral class function parameters, you can use class/struct
/// implementing the CBUUIDConvertible protocol. The protocol is used to give any object a way of converting itself to a CBUUID object.
/// An implementation of the protocol is already provided for the following class: String, NSUUID, CBUUID, CBAttribute
///
/// Using this, you could call discoverServices(...) using multiple different kind of parameters for the "servicesUUIDs" array:
///
/// - discoverServices(["01AF"], ...)
/// - discoverServices(["CBUUID(string: 01AF)"], ...)
/// - discoverServices([CBService], ...)
///
/// See also the list of object already implementing the protocol
public protocol CBUUIDConvertible {
    var CBUUIDRepresentation: CBUUID { get }
}

extension String: CBUUIDConvertible {
    public var CBUUIDRepresentation: CBUUID {
        return CBUUID(string: self)
    }
}

extension UUID: CBUUIDConvertible {
    public var CBUUIDRepresentation: CBUUID {
        return CBUUID(nsuuid: self)
    }
}

extension CBUUID: CBUUIDConvertible {
    public var CBUUIDRepresentation: CBUUID {
        return self
    }
}

extension CBAttribute: CBUUIDConvertible {
    public var CBUUIDRepresentation: CBUUID {
        return self.uuid
    }
}

func ExtractCBUUIDs(_ CBUUIDConvertibles: [CBUUIDConvertible]?) -> [CBUUID]? {
    if let CBUUIDConvertibles = CBUUIDConvertibles , CBUUIDConvertibles.count > 0 {
        return CBUUIDConvertibles.map { $0.CBUUIDRepresentation }
    } else {
        return nil
    }
}


================================================
FILE: Sources/CBUUIDPath.swift
================================================
//
//  CBUUIDPath.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

struct CBUUIDPath: Hashable {
    let hash: Int
    
    init(uuids: CBUUID...) {
        var stringPath: String = String()
        
        for uuid in uuids {
            stringPath.append(uuid.uuidString)
        }
        
        self.hash = stringPath.hashValue
    }
    
}
func ==(lhs: CBUUIDPath, rhs: CBUUIDPath) -> Bool {
    return lhs.hashValue == rhs.hashValue
}

func servicePath(service: CBUUIDConvertible) -> CBUUIDPath {
    return CBUUIDPath(uuids: service.CBUUIDRepresentation)
}

func characteristicPath(service: CBUUIDConvertible,
                        characteristic: CBUUIDConvertible) -> CBUUIDPath {
    return CBUUIDPath(uuids: service.CBUUIDRepresentation,
                      characteristic.CBUUIDRepresentation)
}

func descriptorPath(service: CBUUIDConvertible,
                    characteristic: CBUUIDConvertible,
                    descriptor: CBUUIDConvertible) -> CBUUIDPath {
    return CBUUIDPath(uuids: service.CBUUIDRepresentation,
                      characteristic.CBUUIDRepresentation,
                      descriptor.CBUUIDRepresentation)
}

extension CBService {
    var uuidPath: CBUUIDPath {
        return servicePath(service: self)
    }
}

extension CBCharacteristic {
    var uuidPath: CBUUIDPath {
        return characteristicPath(service: self.service!, characteristic: self)
    }
}

extension CBDescriptor {
    var uuidPath: CBUUIDPath {
        return descriptorPath(service: self.characteristic!.service!, characteristic: self.characteristic!, descriptor: self)
    }
}


================================================
FILE: Sources/Central.swift
================================================
//
//  Central.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

/**
    The different results returned in the closure of the Central scanWithTimeout(...) function.

    - ScanStarted: The scan just started.
    - ScanResult: A Peripheral found result. `RSSI` will be nil if it could not be read.
    - ScanStopped: The scan ended.
 
*/
public enum PeripheralScanResult {
    case scanStarted
    case scanResult(peripheral: Peripheral, advertisementData: [String: Any], RSSI: Int?)
    case scanStopped(peripherals: [Peripheral], error: SBError?)
}

/**
    An enum type whoses rawValues mirror the CBCentralManagerState enum owns Integer values but without the ".Resetting" and ".Unknown" temporary values.

    - Unsupported: CBCentralManagerState.Unsupported
    - Unauthorized: CBCentralManagerState.Unauthorized
    - PoweredOff: CBCentralManagerState.PoweredOff
    - PoweredOn: CBCentralManagerState.PoweredOn

*/
public enum AsyncCentralState: Int {
    case unsupported = 2
    case unauthorized = 3
    case poweredOff = 4
    case poweredOn = 5
    case unknown = -1
}

public typealias AsyncCentralStateCallback = (AsyncCentralState) -> Void
public typealias BluetoothStateCallback = (CBManagerState) -> Void
public typealias PeripheralScanCallback = (PeripheralScanResult) -> Void
public typealias ConnectPeripheralCallback = (Result<Void, Error>) -> Void
public typealias DisconnectPeripheralCallback = (Result<Void, Error>) -> Void

/// A singleton wrapping a CBCentralManager instance to run CBCentralManager related functions with closures based callbacks instead of the usual CBCentralManagerDelegate interface.
public final class Central {
    private static var _sharedInstance: Central?
    
    /// The name of a `Notification` posted by the Central sharedInstance when the app comes back from the background and restores the
    /// underlying CBCentralManager state after the centralManager:willRestoreState: delegate method is called.
    /// The userInfo contains an Array of `Peripheral` that were restored.
    /// Unwrap the peripherals with `notification.userInfo?["peripherals"] as? [Peripheral]`
    public static let CentralManagerWillRestoreState = Notification.Name("SwiftyBluetooth_CentralManagerWillRestoreStateNotification")
    
    /// The name of a `Notification` posted by the Central sharedInstance when its underlying CBCentralManager state changes. Take note that if the `CBCentralManager` state
    /// goes from poweredOn to something lower, all your peripherals will be invalidated and need to be discovered again.
    /// The new `CBCentralManagerState` can be found in the notification's userInfo.
    /// Unwrap with `notification.userInfo?["state"] as? CBCentralManagerState`
    public static let CentralStateChange = Notification.Name("SwiftyBluetooth_CentralStateChange")
    
    static let CentralCBPeripheralDisconnected = Notification.Name("SwiftyBluetooth_CentralCBPeripheralDisconnected")
    
    /// The sharedInstance Singleton, you can instantiate it yourself by
    /// calling `setSharedInstanceWith(restoreIdentifier: )` which will allow you
    /// to pass in a state preservation identifier. Otherwise, this sharedInstance
    /// be lazily initialized the first time you call this getter.
    public static var sharedInstance: Central {
        if let sharedInstance = _sharedInstance {
            return sharedInstance
        }
        _sharedInstance = Central()
        return _sharedInstance!
    }
    
    /// Allows you to initially set the sharedInstance and use the restore
    /// identifier string of your choice for state preservation between app
    /// launches.
    @discardableResult
    public static func setSharedInstanceWith(restoreIdentifier: String) -> Central {
        assert(_sharedInstance == nil, "You can only set the sharedInstance of the Central once and you must do so before calling any other SwiftyBluetooth functions.")
        _sharedInstance = Central(stateRestoreIdentifier: restoreIdentifier)
        return _sharedInstance!
    }
    
    fileprivate let centralProxy: CentralProxy
    
    private init() {
        self.centralProxy = CentralProxy()
    }
    
    private init(stateRestoreIdentifier: String) {
        self.centralProxy = CentralProxy(stateRestoreIdentifier: stateRestoreIdentifier)
    }
    
}

// MARK: Internal
typealias InitializeBluetoothCallback = (_ error: SBError?) -> Void

extension Central {
    func initializeBluetooth(completion: @escaping InitializeBluetoothCallback) {
        centralProxy.initializeBluetooth(completion)
    }
    
    func connect(peripheral: CBPeripheral,
                 timeout: TimeInterval = 10,
                 completion: @escaping ConnectPeripheralCallback) {
        centralProxy.connect(peripheral: peripheral, timeout: timeout, completion)
    }
    
    func disconnect(peripheral: CBPeripheral,
                    timeout: TimeInterval = 10,
                    completion: @escaping DisconnectPeripheralCallback) {
        centralProxy.disconnect(peripheral: peripheral, timeout: timeout, completion)
    }
}

// MARK: Public
extension Central {
    
    /// The underlying CBCentralManager CBManagerState
    public var state: CBManagerState {
        switch self.centralProxy.centralManager.state.rawValue {
        case 0:
            return .unknown
        case 1:
            return .resetting
        case 2:
            return .unsupported
        case 3:
            return .unauthorized
        case 4:
            return .poweredOff
        case 5:
            return .poweredOn
        default:
            assertionFailure("Unhandlable bluetooth state")
            return .unknown
        }
    }
    
    /// The underlying CBCentralManager isScanning value
    public var isScanning: Bool {
        return self.centralProxy.centralManager.isScanning
    }
    
    /// Attempts to return the periperals from a list of identifier "UUID"s
    public func retrievePeripherals(withUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] {
        let uuids = uuids.compactMap { UUID(uuidString: $0.CBUUIDRepresentation.uuidString)  }
        let cbPeripherals = self.centralProxy.centralManager.retrievePeripherals(withIdentifiers: uuids)
        let peripherals = cbPeripherals.map { cbPeripheral -> Peripheral in
            return Peripheral(peripheral: cbPeripheral)
        }
        return peripherals
    }
    
    /// Attempts to return the connected peripheral having the specific service CBUUIDs
    public func retrieveConnectedPeripherals(withServiceUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] {
        let cbUUIDs = ExtractCBUUIDs(uuids) ?? []
        let cbPeripherals = self.centralProxy.centralManager.retrieveConnectedPeripherals(withServices: cbUUIDs)
        let peripherals = cbPeripherals.map { cbPeripheral -> Peripheral in
            return Peripheral(peripheral: cbPeripheral)
        }
        return peripherals
    }
    
    /// Scans for Peripherals through a CBCentralManager scanForPeripheralsWithServices(...) function call.
    ///
    /// - Parameter timeout: The scanning time in seconds before the scan is stopped and the completion closure is called with a scanStopped result.
    /// - Parameter serviceUUIDs: The service UUIDs to search peripherals for or nil if looking for all peripherals.
    /// - Parameter completion: The closures, called multiple times throughout a scan.
    public func scanForPeripherals(withServiceUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil,
                                   options: [String : Any]? = nil,
                                   timeoutAfter timeout: TimeInterval,
                                   completion: @escaping PeripheralScanCallback) {
        // Passing in an empty array will act the same as if you passed nil and discover all peripherals but
        // it is recommended to pass in nil for those cases similarly to how the CoreBluetooth scan method works
        assert(serviceUUIDs == nil || serviceUUIDs!.count > 0)
        centralProxy.scanWithTimeout(timeout, serviceUUIDs: ExtractCBUUIDs(serviceUUIDs), options: options, completion)
    }
    
    /// Will stop the current scan through a CBCentralManager stopScan() function call and invokes the completion
    /// closures of the original scanWithTimeout function call with a scanStopped result containing an error if something went wrong.
    public func stopScan() {
        centralProxy.stopScan()
    }
    
    /// Sometime, the bluetooth state of your iOS Device/CBCentralManagerState is in an inbetween state of either
    /// ".Unknown" or ".Reseting". This function will wait until the bluetooth state is stable and return a subset
    /// of the CBCentralManager state value which does not includes these values in its completion closure.
    public func asyncState(completion: @escaping AsyncCentralStateCallback) {
        self.centralProxy.asyncState(completion)
    }
    
}


================================================
FILE: Sources/CentralProxy.swift
================================================
//
//  CentralProxy.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

final class CentralProxy: NSObject {
    fileprivate lazy var asyncStateCallbacks: [AsyncCentralStateCallback] = []
    
    fileprivate var scanRequest: PeripheralScanRequest?
    
    fileprivate lazy var connectRequests: [UUID: ConnectPeripheralRequest] = [:]
    fileprivate lazy var disconnectRequests: [UUID: DisconnectPeripheralRequest] = [:]
    
    var centralManager: CBCentralManager!
    
    override init() {
        super.init()
        
        self.centralManager = CBCentralManager(delegate: self, queue: nil)
    }
    
    init(stateRestoreIdentifier: String) {
        super.init()
        
        self.centralManager = CBCentralManager(delegate: self, queue: nil, options: [CBCentralManagerOptionRestoreIdentifierKey: stateRestoreIdentifier])
    }
    
    fileprivate func postCentralEvent(_ event: NSNotification.Name, userInfo: [AnyHashable: Any]? = nil) {
        NotificationCenter.default.post(
            name: event,
            object: Central.sharedInstance,
            userInfo: userInfo)
    }
}

// MARK: Initialize Bluetooth requests
extension CentralProxy {
    func asyncState(_ completion: @escaping AsyncCentralStateCallback) {
        switch centralManager.state {
        case .unknown:
            self.asyncStateCallbacks.append(completion)
        case .resetting:
            self.asyncStateCallbacks.append(completion)
        case .unsupported:
            completion(.unsupported)
        case .unauthorized:
            completion(.unauthorized)
        case .poweredOff:
            completion(.poweredOff)
        case .poweredOn:
            completion(.poweredOn)
        @unknown default:
            completion(.unknown)
        }
    }
    
    func initializeBluetooth(_ completion: @escaping InitializeBluetoothCallback) {
        self.asyncState { (state) in
            switch state {
            case .unsupported:
                completion(.bluetoothUnavailable(reason: .unsupported))
            case .unauthorized:
                completion(.bluetoothUnavailable(reason: .unauthorized))
            case .poweredOff:
                completion(.bluetoothUnavailable(reason: .poweredOff))
            case .poweredOn:
                completion(nil)
            case .unknown:
                completion(.bluetoothUnavailable(reason: .unknown))
            }
        }
    }
    
    func callAsyncCentralStateCallback(_ state: AsyncCentralState) {
        let callbacks = self.asyncStateCallbacks
        
        self.asyncStateCallbacks.removeAll()
        
        for callback in callbacks {
            callback(state)
        }
    }
}

// MARK: Scan requests
private final class PeripheralScanRequest {
    let callback: PeripheralScanCallback
    var peripherals: [UUID: Peripheral] = [:]
    
    init(callback: @escaping PeripheralScanCallback) {
        self.callback = callback
    }
}

extension CentralProxy {
    func scanWithTimeout(_ timeout: TimeInterval, serviceUUIDs: [CBUUID]?, options: [String : Any]?, _ callback: @escaping PeripheralScanCallback) {
        initializeBluetooth { [unowned self] (error) in
            if let error = error {
                callback(PeripheralScanResult.scanStopped(peripherals: [], error: error))
            } else {
                if self.scanRequest != nil {
                    self.centralManager.stopScan()
                }
                
                let scanRequest = PeripheralScanRequest(callback: callback)
                self.scanRequest = scanRequest
                
                scanRequest.callback(.scanStarted)
                self.centralManager.scanForPeripherals(withServices: serviceUUIDs, options: options)
                
                Timer.scheduledTimer(
                    timeInterval: timeout,
                    target: self,
                    selector: #selector(self.onScanTimerTick),
                    userInfo: Weak(value: scanRequest),
                    repeats: false)
            }
        }
    }
    
    func stopScan(error: SBError? = nil) {

        // avoid an API MISUSE warning on the console if bluetooth is powered off or unsupported
        if self.centralManager.state != .poweredOff, self.centralManager.state != .unsupported {
            self.centralManager.stopScan()
        }

        if let scanRequest = self.scanRequest {
            self.scanRequest = nil
            scanRequest.callback(.scanStopped(peripherals: scanRequest.peripherals.values.map({$0}), error: error))
        }
    }
    
    @objc fileprivate func onScanTimerTick(_ timer: Timer) {
        
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<PeripheralScanRequest>
        
        if weakRequest.value != nil {
            self.stopScan()
        }
    }
}

// MARK: Connect Peripheral requests
private final class ConnectPeripheralRequest {
    var callbacks: [ConnectPeripheralCallback] = []
    
    let peripheral: CBPeripheral
    
    init(peripheral: CBPeripheral, callback: @escaping ConnectPeripheralCallback) {
        self.callbacks.append(callback)
        
        self.peripheral = peripheral
    }
    
    func invokeCallbacks(error: Error?) {
        let result: Result<Void, Error> = {
            if let error = error {
                return .failure(error)
            } else {
                return .success(())
            }
        }()
        for callback in callbacks {
            callback(result)
        }
    }
}

extension CentralProxy {
    func connect(peripheral: CBPeripheral, timeout: TimeInterval, _ callback: @escaping ConnectPeripheralCallback) {
        initializeBluetooth { [unowned self] (error) in
            if let error = error {
                callback(.failure(error))
                return
            }
            
            let uuid = peripheral.identifier
            
            if let cbPeripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first , cbPeripheral.state == .connected {
                callback(.success(()))
                return
            }
            
            if let request = self.connectRequests[uuid] {
                request.callbacks.append(callback)
            } else {
                let request = ConnectPeripheralRequest(peripheral: peripheral, callback: callback)
                self.connectRequests[uuid] = request
                
                self.centralManager.connect(peripheral, options: nil)
                Timer.scheduledTimer(
                    timeInterval: timeout,
                    target: self,
                    selector: #selector(self.onConnectTimerTick),
                    userInfo: Weak(value: request),
                    repeats: false)
            }
        }
    }
    
    @objc fileprivate func onConnectTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<ConnectPeripheralRequest>
        guard let request = weakRequest.value else {
            return
        }
        
        let uuid = request.peripheral.identifier
        
        self.connectRequests[uuid] = nil
        
        self.centralManager.cancelPeripheralConnection(request.peripheral)
        
        request.invokeCallbacks(error: SBError.operationTimedOut(operation: .connectPeripheral))
    }
}

// MARK: Disconnect Peripheral requests
private final class DisconnectPeripheralRequest {
    var callbacks: [ConnectPeripheralCallback] = []
    
    let peripheral: CBPeripheral
    
    init(peripheral: CBPeripheral, callback: @escaping DisconnectPeripheralCallback) {
        self.callbacks.append(callback)
        
        self.peripheral = peripheral
    }
    
    func invokeCallbacks(error: Error?) {
        let result: Result<Void, Error> = {
            if let error = error {
                return .failure(error)
            } else {
                return .success(())
            }
        }()
        for callback in callbacks {
            callback(result)
        }
    }
}

extension CentralProxy {
    func disconnect(peripheral: CBPeripheral, timeout: TimeInterval, _ callback: @escaping DisconnectPeripheralCallback) {
        initializeBluetooth { [unowned self] (error) in
            
            if let error = error {
                callback(.failure(error))
                return
            }
            
            let uuid = peripheral.identifier
            
            if let cbPeripheral = self.centralManager.retrievePeripherals(withIdentifiers: [uuid]).first,
                (cbPeripheral.state == .disconnected || cbPeripheral.state == .disconnecting) {
                callback(.success(()))
                return
            }
            
            if let request = self.disconnectRequests[uuid] {
                request.callbacks.append(callback)
            } else {
                let request = DisconnectPeripheralRequest(peripheral: peripheral, callback: callback)
                self.disconnectRequests[uuid] = request
                
                self.centralManager.cancelPeripheralConnection(peripheral)
                Timer.scheduledTimer(
                    timeInterval: timeout,
                    target: self,
                    selector: #selector(self.onDisconnectTimerTick),
                    userInfo: Weak(value: request),
                    repeats: false)
            }
        }
    }
    
    @objc fileprivate func onDisconnectTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<DisconnectPeripheralRequest>
        guard let request = weakRequest.value else {
            return
        }
        
        let uuid = request.peripheral.identifier
        
        self.disconnectRequests[uuid] = nil
        
        request.invokeCallbacks(error: SBError.operationTimedOut(operation: .disconnectPeripheral))
    }
}

extension CentralProxy: CBCentralManagerDelegate {
    
    func centralManagerDidUpdateState(_ central: CBCentralManager) {
        postCentralEvent(Central.CentralStateChange, userInfo: ["state": central.state])
        
        switch central.state.rawValue {
        case 0: // .unknown
            self.stopScan(error: .scanningEndedUnexpectedly)
        case 1: // .resetting
            self.stopScan(error: .scanningEndedUnexpectedly)
        case 2: // .unsupported
            self.callAsyncCentralStateCallback(.unsupported)
            self.stopScan(error: .scanningEndedUnexpectedly)
        case 3: // .unauthorized
            self.callAsyncCentralStateCallback(.unauthorized)
            self.stopScan(error: .scanningEndedUnexpectedly)
        case 4: // .poweredOff
            self.callAsyncCentralStateCallback(.poweredOff)
            self.stopScan(error: .scanningEndedUnexpectedly)
        case 5: // .poweredOn
            self.callAsyncCentralStateCallback(.poweredOn)
        default:
            fatalError("Unsupported BLE CentralState")
        }
    }
    
    func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
        let uuid = peripheral.identifier
        guard let request = connectRequests[uuid] else {
            return
        }
        
        connectRequests[uuid] = nil
        
        request.invokeCallbacks(error: nil)
    }
    
    func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
        let uuid = peripheral.identifier
        
        var userInfo: [AnyHashable: Any] = ["identifier": uuid]
        if let error = error {
            userInfo["error"] = error
        }
        
        postCentralEvent(Central.CentralCBPeripheralDisconnected, userInfo: userInfo)
        
        guard let request = disconnectRequests[uuid] else {
            return
        }
        
        disconnectRequests[uuid] = nil
        request.invokeCallbacks(error: error)
    }
    
    func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
        let uuid = peripheral.identifier
        guard let request = connectRequests[uuid] else {
            return
        }
        
        let resolvedError: Error = error ?? SBError.peripheralFailedToConnectReasonUnknown
        
        connectRequests[uuid] = nil
        
        request.invokeCallbacks(error: resolvedError)
    }
    
    func centralManager(_ central: CBCentralManager,
                        didDiscover peripheral: CBPeripheral,
                        advertisementData: [String: Any],
                        rssi RSSI: NSNumber)
    {
        guard let scanRequest = scanRequest else {
            return
        }
        
        guard scanRequest.peripherals[peripheral.identifier] == nil else { return }
        
        let peripheral = Peripheral(peripheral: peripheral)
        scanRequest.peripherals[peripheral.identifier] = peripheral
        
        var rssiOptional: Int? = Int(truncating: RSSI)
        if let rssi = rssiOptional, rssi == 127 {
            rssiOptional = nil
        }
        
        scanRequest.callback(.scanResult(peripheral: peripheral, advertisementData: advertisementData, RSSI: rssiOptional))
    }
    
    func centralManager(_ central: CBCentralManager, willRestoreState dict: [String: Any]) {
        let peripherals = ((dict[CBCentralManagerRestoredStatePeripheralsKey] as? [CBPeripheral]) ?? []).map { Peripheral(peripheral: $0) }
        postCentralEvent(Central.CentralManagerWillRestoreState, userInfo: ["peripherals": peripherals])
    }

}


================================================
FILE: Sources/DescriptorValue.swift
================================================
//
//  DescriptorValue.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

/**
    Wrapper around common GATT descriptor values. Automatically unwrap and cast your descriptor values for
    standard GATT descriptor UUIDs.

    - CharacteristicExtendedProperties: Case for Descriptor with UUID CBUUIDCharacteristicExtendedPropertiesString
    - CharacteristicUserDescription: Case for Descriptor with UUID CBUUIDCharacteristicUserDescriptionString
    - ClientCharacteristicConfigurationString: Case for Descriptor with UUID CBUUIDClientCharacteristicConfigurationString
    - ServerCharacteristicConfigurationString: Case for Descriptor with UUID CBUUIDServerCharacteristicConfigurationString
    - CharacteristicFormatString: Case for Descriptor with UUID CBUUIDCharacteristicFormatString
    - CharacteristicAggregateFormatString: Case for Descriptor with UUID CBUUIDCharacteristicAggregateFormatString
    - CustomValue: Case for descriptor with a non standard UUID
*/
public enum DescriptorValue {
    case characteristicExtendedProperties(value: UInt16)
    case characteristicUserDescription(value: String)
    case clientCharacteristicConfigurationString(value: UInt16)
    case serverCharacteristicConfigurationString(value: UInt16)
    case characteristicFormatString(value: Data)
    case characteristicAggregateFormatString(value: UInt16)
    case customValue(value: AnyObject)
    
    init(descriptor: CBDescriptor) throws {
        guard let value = descriptor.value else {
            throw SBError.invalidDescriptorValue(descriptor: descriptor)
        }
        
        switch descriptor.CBUUIDRepresentation.uuidString {
        case CBUUIDCharacteristicExtendedPropertiesString:
            guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .characteristicExtendedProperties(value: value)
            
        case CBUUIDCharacteristicUserDescriptionString:
            guard let value = descriptor.value as? String else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .characteristicUserDescription(value: value)
            
        case CBUUIDClientCharacteristicConfigurationString:
            guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .clientCharacteristicConfigurationString(value: value)
            
        case CBUUIDServerCharacteristicConfigurationString:
            guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .serverCharacteristicConfigurationString(value: value)
            
        case CBUUIDCharacteristicFormatString:
            guard let value = descriptor.value as? Data else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .characteristicFormatString(value: value)
            
        case CBUUIDCharacteristicAggregateFormatString:
            guard let value = UInt16(uncastedUnwrappedNSNumber: descriptor.value as AnyObject?) else {
                throw SBError.invalidDescriptorValue(descriptor: descriptor)
            }
            self = .characteristicAggregateFormatString(value: value)
            
        default:
            self = .customValue(value: value as AnyObject)
        }
    }
}


================================================
FILE: Sources/Info.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>CFBundleDevelopmentRegion</key>
	<string>en</string>
	<key>CFBundleExecutable</key>
	<string>$(EXECUTABLE_NAME)</string>
	<key>CFBundleIdentifier</key>
	<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
	<key>CFBundleInfoDictionaryVersion</key>
	<string>6.0</string>
	<key>CFBundleName</key>
	<string>$(PRODUCT_NAME)</string>
	<key>CFBundlePackageType</key>
	<string>FMWK</string>
	<key>CFBundleShortVersionString</key>
	<string>1.0.0</string>
	<key>CFBundleSignature</key>
	<string>????</string>
	<key>CFBundleVersion</key>
	<string>$(CURRENT_PROJECT_VERSION)</string>
	<key>NSPrincipalClass</key>
	<string></string>
</dict>
</plist>


================================================
FILE: Sources/Peripheral.swift
================================================
//
//  Peripheral.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

/**
    The Peripheral notifications sent through the default 'NotificationCenter' by Peripherals.
 
    Use the PeripheralEvent enum rawValue as the notification string when registering for notifications.
 
    - peripheralModifedServices: Update to a peripheral's CBPeripheral services, userInfo: ["invalidatedServices": [CBService]]
    - characteristicValueUpdate: An update to the value of a characteristic you're peripherals is subscribed for updates from, userInfo: ["characteristic": CBCharacteristic, "error": SBError?]
*/


public typealias rssi = Int
public typealias isNotifying = Bool

public typealias ReadRSSIRequestCallback = (_ result: Result<rssi, Error>) -> Void
public typealias ServiceRequestCallback = (_ result: Result<[CBService], Error>) -> Void
public typealias CharacteristicRequestCallback = (_ result: Result<[CBCharacteristic], Error>) -> Void
public typealias DescriptorRequestCallback = (_ result: Result<[CBDescriptor], Error>) -> Void
public typealias ReadCharacRequestCallback = (_ result: Result<Data, Error>) -> Void
public typealias ReadDescriptorRequestCallback = (_ result: Result<DescriptorValue, Error>) -> Void
public typealias WriteRequestCallback = (_ result: Result<Void, Error>) -> Void
public typealias UpdateNotificationStateCallback = (_ result: Result<isNotifying, Error>) -> Void

/// An interface on top of a CBPeripheral instance used to run CBPeripheral related functions with closures based callbacks instead of the usual CBPeripheralDelegate interface.
public final class Peripheral {
    fileprivate var peripheralProxy: PeripheralProxy!
    
    init(peripheral: CBPeripheral) {
        self.peripheralProxy = PeripheralProxy(cbPeripheral: peripheral, peripheral: self)
    }
}

// MARK: Public
extension Peripheral {
    /// The name of a `Notification` posted by a `Peripheral` instance when its `CBPeripheral` name value changes.
    /// Unwrap the new name if available with `notification.userInfo?["name"] as? String`
    public static let PeripheralNameUpdate = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralNameUpdate")
    
    /// The name of a `Notification` posted by a `Peripheral` instance when some of its `CBPeripheral` services are invalidated.
    /// Unwrap the invalidated services with `notification.userInfo?["invalidatedServices"] as? [CBSErvice]`
    public static let PeripheralModifedServices = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralModifedServices")
    
    /// The name of a `Notification` posted by a `Peripheral` instance when one of the characteristic you have subcribed for update from
    /// changes its value.
    /// Unwrap the new charac value with `notification.userInfo?["characteristic"] as? CBCharacteristic`
    /// Unwrap the error if any with `notification.userInfo?["error"] as? SBError`
    public static let PeripheralCharacteristicValueUpdate = Notification.Name(rawValue: "SwiftyBluetooth_PharacteristicValueUpdate")
    
    
    /// The name of a `Notification` posted by a `Peripheral` instance when it becomes disconnected
    /// Unwrap the new charac value with `notification.userInfo?["characteristic"] as? CBCharacteristic`
    /// Unwrap the error if any with `notification.userInfo?["error"] as? SBError`
    public static let PeripheralDisconnected = Notification.Name(rawValue: "SwiftyBluetooth_PeripheralDisconnected")
    
    /// The underlying CBPeripheral identifier
    public var identifier: UUID {
        return self.peripheralProxy.cbPeripheral.identifier
    }
    
    /// The underlying CBPeripheral name
    public var name: String? {
        return self.peripheralProxy.cbPeripheral.name
    }
    
    /// The underlying CBPeripheral state
    public var state: CBPeripheralState {
        return self.peripheralProxy.cbPeripheral.state
    }
    
    /// The underlying CBPeripheral services
    public var services: [CBService]? {
        return self.peripheralProxy.cbPeripheral.services
    }
    
    /// Returns the service requested if it exists and has been discovered
    public func service(withUUID serviceUUID: CBUUIDConvertible) -> CBService? {
        return self.peripheralProxy.cbPeripheral
            .serviceWithUUID(serviceUUID.CBUUIDRepresentation)
    }
    
    /// Returns the characteristic requested if it exists and has been discovered
    public func characteristic(withUUID characteristicUUID: CBUUIDConvertible,
                               ofServiceWithUUID serviceUUID: CBUUIDConvertible) -> CBCharacteristic? {
        return self.peripheralProxy.cbPeripheral
            .serviceWithUUID(serviceUUID.CBUUIDRepresentation)?
            .characteristicWithUUID(characteristicUUID.CBUUIDRepresentation)
    }
    
    /// Returns the descriptor requested if it exists and has been discovered
    public func descriptor(withUUID descriptorUUID: CBUUIDConvertible,
                           ofCharacWithUUID characUUID: CBUUIDConvertible,
                           fromServiceWithUUID serviceUUID: CBUUIDConvertible) -> CBDescriptor? {
        return self.peripheralProxy.cbPeripheral
            .serviceWithUUID(serviceUUID.CBUUIDRepresentation)?
            .characteristicWithUUID(characUUID.CBUUIDRepresentation)?
            .descriptorWithUUID(descriptorUUID.CBUUIDRepresentation)
    }
    
    /// Connect to the peripheral through Ble to our Central sharedInstance
    public func connect(withTimeout timeout: TimeInterval?, completion: @escaping ConnectPeripheralCallback) {
        if let timeout = timeout {
            self.peripheralProxy.connect(timeout: timeout, completion)
        } else {
            self.peripheralProxy.connect(timeout: TimeInterval.infinity, completion)
        }
    }
    
    /// Disconnect the peripheral from our Central sharedInstance
    public func disconnect(completion: @escaping DisconnectPeripheralCallback) {
        self.peripheralProxy.disconnect(completion)
    }
    
    /// Connects to the peripheral and update the Peripheral's RSSI through a 'CBPeripheral' readRSSI() function call
    ///
    /// - Parameter completion: A closure containing the integer value of the updated RSSI or an error.
    public func readRSSI(completion: @escaping ReadRSSIRequestCallback) {
        self.peripheralProxy.readRSSI(completion)
    }
    
    /// Connects to the peripheral and discover the requested services through a 'CBPeripheral' discoverServices(...) function call
    ///
    /// - Parameter serviceUUIDs: The UUIDs of the services you want to discover or nil if you want to discover all services.
    /// - Parameter completion: A closures containing an array of the services found or an error.
    public func discoverServices(withUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil,
                                 completion: @escaping ServiceRequestCallback)
    {
        // Passing in an empty array will act the same as if you passed nil and discover all the services.
        // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverServices method works
        assert(serviceUUIDs == nil || serviceUUIDs!.count > 0)
        self.peripheralProxy.discoverServices(ExtractCBUUIDs(serviceUUIDs), completion: completion)
    }
    
    /// Connects to the peripheral and discover the requested included services of a service through a 'CBPeripheral' discoverIncludedServices(...) function call
    ///
    /// - Parameter serviceUUIDs: The UUIDs of the included services you want to discover or nil if you want to discover all included services.
    /// - Parameter serviceUUID: The service to request included services from.
    /// - Parameter completion: A closures containing an array of the services found or an error.
    public func discoverIncludedServices(withUUIDs includedServiceUUIDs: [CBUUIDConvertible]? = nil,
                                         ofServiceWithUUID serviceUUID: CBUUIDConvertible,
                                         completion: @escaping ServiceRequestCallback)
    {
        // Passing in an empty array will act the same as if you passed nil and discover all the services.
        // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverServices method works
        assert(includedServiceUUIDs == nil || includedServiceUUIDs!.count > 0)
        self.peripheralProxy.discoverIncludedServices(ExtractCBUUIDs(includedServiceUUIDs),
                                                      forService: serviceUUID.CBUUIDRepresentation,
                                                      completion: completion)
    }
    
    /// Connects to the peripheral and discover the requested characteristics through a 'CBPeripheral' discoverCharacteristics(...) function call.
    /// Will first discover the service of the requested characteristics if necessary.
    ///
    /// - Parameter serviceUUID: The UUID of the service of the characteristics requested.
    /// - Parameter characteristicUUIDs: The UUIDs of the characteristics you want to discover or nil if you want to discover all characteristics.
    /// - Parameter completion: A closures containing an array of the characteristics found or an error.
    public func discoverCharacteristics(withUUIDs characteristicUUIDs: [CBUUIDConvertible]? = nil,
                                        ofServiceWithUUID serviceUUID: CBUUIDConvertible,
                                        completion: @escaping CharacteristicRequestCallback)
    {
        // Passing in an empty array will act the same as if you passed nil and discover all the characteristics.
        // But it is recommended to pass in nil for those cases similarly to how the CoreBluetooth discoverCharacteristics method works
        assert(characteristicUUIDs == nil || characteristicUUIDs!.count > 0)
        self.peripheralProxy.discoverCharacteristics(ExtractCBUUIDs(characteristicUUIDs),
                                                     forService: serviceUUID.CBUUIDRepresentation,
                                                     completion: completion)
    }
    
    /// Connects to the peripheral and discover the requested descriptors through a 'CBPeripheral' discoverDescriptorsForCharacteristic(...) function call.
    /// Will first discover the service and characteristic for which you want to discover descriptors from.
    ///
    /// - Parameter characteristicUUID: The UUID of the characteristic you want to discover descriptors from.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter completion: A closures containing an array of the descriptors found or an error.
    public func discoverDescriptors(ofCharacWithUUID characUUID: CBUUIDConvertible,
                                    fromServiceWithUUID serviceUUID: CBUUIDConvertible,
                                    completion: @escaping DescriptorRequestCallback)
    {
        self.peripheralProxy.discoverDescriptorsForCharacteristic(characUUID.CBUUIDRepresentation,
                                                                  serviceUUID: serviceUUID.CBUUIDRepresentation,
                                                                  completion: completion)
    }
    
    /// Connects to the peripheral and discover the requested descriptors through a 'CBPeripheral' discoverDescriptorsForCharacteristic(...) function call.
    /// Will first discover the service and characteristic for which you want to discover descriptors from.
    ///
    /// - Parameter charac: The characteristic to discover descriptors from.
    /// - Parameter completion: A closures containing an array of the descriptors found or an error.
    public func discoverDescriptors(ofCharac charac: CBCharacteristic,
                                    completion: @escaping DescriptorRequestCallback)
    {
        self.discoverDescriptors(ofCharacWithUUID: charac,
                                 fromServiceWithUUID: charac.service!,
                                 completion: completion)
    }
    
    /// Connect to the peripheral and read the value of the characteristic requested through a 'CBPeripheral' readValueForCharacteristic(...) function call.
    /// Will first discover the service and characteristic you want to read from if necessary.
    ///
    /// - Parameter characteristicUUID: The UUID of the characteristic you want to read from.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter completion: A closures containing the data read or an error.
    public func readValue(ofCharacWithUUID characUUID: CBUUIDConvertible,
                          fromServiceWithUUID serviceUUID: CBUUIDConvertible,
                          completion: @escaping ReadCharacRequestCallback)
    {
        self.peripheralProxy.readCharacteristic(characUUID.CBUUIDRepresentation,
                                                serviceUUID: serviceUUID.CBUUIDRepresentation,
                                                completion: completion)
    }
    
    /// Connect to the peripheral and read the value of the passed characteristic through a 'CBPeripheral' readValueForCharacteristic(...) function call.
    ///
    /// - Parameter charac: The characteristic you want to read from.
    /// - Parameter completion: A closures containing the data read or an error.
    public func readValue(ofCharac charac: CBCharacteristic,
                          completion: @escaping ReadCharacRequestCallback)
    {
        self.readValue(ofCharacWithUUID: charac,
                       fromServiceWithUUID: charac.service!,
                       completion: completion)
    }
    
    /// Connect to the peripheral and read the value of the descriptor requested through a 'CBPeripheral' readValueForDescriptor(...) function call.
    /// Will first discover the service, characteristic and descriptor you want to read from if necessary.
    ///
    /// - Parameter descriptorUUID: The UUID of the descriptor you want to read from.
    /// - Parameter characteristicUUID: The UUID of the descriptor above.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter completion: A closures containing the data read or an error.
    public func readValue(ofDescriptorWithUUID descriptorUUID: CBUUIDConvertible,
                          fromCharacUUID characUUID: CBUUIDConvertible,
                          ofServiceUUID serviceUUID: CBUUIDConvertible,
                          completion: @escaping ReadDescriptorRequestCallback)
    {
        self.peripheralProxy.readDescriptor(descriptorUUID.CBUUIDRepresentation,
                                            characteristicUUID: characUUID.CBUUIDRepresentation,
                                            serviceUUID: serviceUUID.CBUUIDRepresentation,
                                            completion: completion)
    }
    
    /// Connect to the peripheral and read the value of the passed descriptor through a 'CBPeripheral' readValueForDescriptor(...) function call.
    ///
    /// - Parameter descriptor: The descriptor you want to read from.
    /// - Parameter characteristicUUID: The UUID of the descriptor above.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter completion: A closures containing the data read or an error.
    public func readValue(ofDescriptor descriptor: CBDescriptor,
                          completion: @escaping ReadDescriptorRequestCallback)
    {
        self.readValue(ofDescriptorWithUUID: descriptor,
                       fromCharacUUID: descriptor.characteristic!,
                       ofServiceUUID: descriptor.characteristic!.service!,
                       completion: completion)
    }
    
    /// Connect to the peripheral and write a value to the characteristic requested through a 'CBPeripheral' writeValue:forCharacteristic(...) function call.
    /// Will first discover the service and characteristic you want to write to if necessary.
    ///
    /// - Parameter characUUID: The UUID of the characteristic you want to write to.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter value: The data being written to the characteristic
    /// - Parameter type: The type of the CBPeripheral write, wither with or without response in which case the closure is called right away
    /// - Parameter completion: A closures containing an error if something went wrong
    public func writeValue(ofCharacWithUUID characUUID: CBUUIDConvertible,
                           fromServiceWithUUID serviceUUID: CBUUIDConvertible,
                           value: Data,
                           type: CBCharacteristicWriteType = .withResponse,
                           completion: @escaping WriteRequestCallback)
    {
        self.peripheralProxy.writeCharacteristicValue(characUUID.CBUUIDRepresentation,
                                                      serviceUUID: serviceUUID.CBUUIDRepresentation,
                                                      value: value,
                                                      type: type,
                                                      completion: completion)
    }
    
    /// Connect to the peripheral and write a value to the passed characteristic through a 'CBPeripheral' writeValue:forCharacteristic(...) function call.
    ///
    /// - Parameter charac: The characteristic you want to write to.
    /// - Parameter value: The data being written to the characteristic
    /// - Parameter type: The type of the CBPeripheral write, wither with or without response in which case the closure is called right away
    /// - Parameter completion: A closures containing an error if something went wrong
    public func writeValue(ofCharac charac: CBCharacteristic,
                           value: Data,
                           type: CBCharacteristicWriteType = .withResponse,
                           completion: @escaping WriteRequestCallback)
    {
        self.writeValue(ofCharacWithUUID: charac,
                        fromServiceWithUUID: charac.service!,
                        value: value,
                        type: type,
                        completion: completion)
    }
    
    /// Connect to the peripheral and write a value to the descriptor requested through a 'CBPeripheral' writeValue:forDescriptor(...) function call.
    /// Will first discover the service, characteristic and descriptor you want to write to if necessary.
    ///
    /// - Parameter descriptorUUID: The UUID of the descriptor you want to write to.
    /// - Parameter characUUID: The UUID of the characteristic of the descriptor above.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter value: The data being written to the characteristic.
    /// - Parameter completion: A closures containing an error if something went wrong.
    public func writeValue(ofDescriptorWithUUID descriptorUUID: CBUUIDConvertible,
                           fromCharacWithUUID characUUID: CBUUIDConvertible,
                           ofServiceWithUUID serviceUUID: CBUUIDConvertible,
                           value: Data,
                           completion: @escaping WriteRequestCallback)
    {
        self.peripheralProxy.writeDescriptorValue(descriptorUUID.CBUUIDRepresentation,
                                                  characteristicUUID: characUUID.CBUUIDRepresentation,
                                                  serviceUUID: serviceUUID.CBUUIDRepresentation,
                                                  value: value,
                                                  completion: completion)
    }
    
    /// Connect to the peripheral and write a value to the passed descriptor through a 'CBPeripheral' writeValue:forDescriptor(...) function call.
    ///
    /// - Parameter descriptor: The descriptor you want to write to.
    /// - Parameter value: The data being written to the descriptor.
    /// - Parameter completion: A closures containing an error if something went wrong.
    public func writeValue(ofDescriptor descriptor: CBDescriptor,
                           value: Data,
                           completion: @escaping WriteRequestCallback)
    {
        self.writeValue(ofDescriptorWithUUID: descriptor,
                        fromCharacWithUUID: descriptor.characteristic!,
                        ofServiceWithUUID: descriptor.characteristic!.service!,
                        value: value,
                        completion: completion)
    }
    
    /// Connect to the peripheral and set the notification value of the characteristic requested through a 'CBPeripheral' setNotifyValueForCharacteristic function call.
    /// Will first discover the service and characteristic you want to either, start, or stop, getting notifcations from.
    ///
    /// - Parameter enabled: If enabled is true, this peripherals will register for change notifcations to the characteristic
    ///      and notify listeners through the default 'NotificationCenter' with a 'PeripheralEvent.characteristicValueUpdate' notification.
    /// - Parameter characUUID: The UUID of the characteristic you want set the notify value of.
    /// - Parameter serviceUUID: The UUID of the service of the characteristic above.
    /// - Parameter completion: A closures containing the updated notification value of the characteristic or an error if something went wrong.
    public func setNotifyValue(toEnabled enabled: Bool,
                               forCharacWithUUID characUUID: CBUUIDConvertible,
                               ofServiceWithUUID serviceUUID: CBUUIDConvertible,
                               completion: @escaping UpdateNotificationStateCallback)
    {
        self.peripheralProxy.setNotifyValueForCharacteristic(enabled,
                                                             characteristicUUID: characUUID.CBUUIDRepresentation,
                                                             serviceUUID: serviceUUID.CBUUIDRepresentation,
                                                             completion: completion)
    }
    
    /// Connect to the peripheral and set the notification value of the passed characteristic through a 'CBPeripheral' setNotifyValueForCharacteristic function call.
    ///
    /// If set to true, this peripheral will emit characteristic change updates through the default NotificationCenter using the "characteristicValueUpdate" notification.
    ///
    /// - Parameter enabled: The notify state of the charac, set enabled to true to receive change notifications through the default Notification center
    /// - Parameter charac: The characteristic you want set the notify value of.
    /// - Parameter completion: A closures containing the updated notification value of the characteristic or an error if something went wrong.
    public func setNotifyValue(toEnabled enabled: Bool,
                               ofCharac charac: CBCharacteristic,
                               completion: @escaping UpdateNotificationStateCallback)
    {
        self.setNotifyValue(toEnabled: enabled,
                            forCharacWithUUID: charac,
                            ofServiceWithUUID: charac.service!,
                            completion: completion)
    }
    
}


================================================
FILE: Sources/PeripheralProxy.swift
================================================
//
//  PeripheralProxy.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

final class PeripheralProxy: NSObject  {
    static let defaultTimeoutInS: TimeInterval = 10
    
    fileprivate lazy var readRSSIRequests: [ReadRSSIRequest] = []
    fileprivate lazy var serviceRequests: [ServiceRequest] = []
    fileprivate lazy var includedServicesRequests: [IncludedServicesRequest] = []
    fileprivate lazy var characteristicRequests: [CharacteristicRequest] = []
    fileprivate lazy var descriptorRequests: [DescriptorRequest] = []
    fileprivate lazy var readCharacteristicRequests: [CBUUIDPath: [ReadCharacteristicRequest]] = [:]
    fileprivate lazy var readDescriptorRequests: [CBUUIDPath: [ReadDescriptorRequest]] = [:]
    fileprivate lazy var writeCharacteristicValueRequests: [CBUUIDPath: [WriteCharacteristicValueRequest]] = [:]
    fileprivate lazy var writeDescriptorValueRequests: [CBUUIDPath: [WriteDescriptorValueRequest]] = [:]
    fileprivate lazy var updateNotificationStateRequests: [CBUUIDPath: [UpdateNotificationStateRequest]] = [:]
    
    fileprivate weak var peripheral: Peripheral?
    let cbPeripheral: CBPeripheral
    
    // Peripheral that are no longer valid must be rediscovered again (happens when for example the Bluetooth is turned off
    // from a user's phone and turned back on
    var valid: Bool = true
    
    init(cbPeripheral: CBPeripheral, peripheral: Peripheral) {
        self.cbPeripheral = cbPeripheral
        self.peripheral = peripheral
        
        super.init()
        
        cbPeripheral.delegate = self
        
        NotificationCenter.default.addObserver(forName: Central.CentralCBPeripheralDisconnected,
                                               object: Central.sharedInstance,
                                               queue: nil)
        { [weak self] (notification) in
            if let identifier = notification.userInfo?["identifier"] as? UUID, identifier == self?.cbPeripheral.identifier {
                self?.postPeripheralEvent(Peripheral.PeripheralDisconnected, userInfo: notification.userInfo)
            }
        }
        
        NotificationCenter.default.addObserver(forName: Central.CentralStateChange,
                                               object: Central.sharedInstance,
                                               queue: nil)
        { [weak self] (notification) in
            if let state = notification.userInfo?["state"] as? CBManagerState, state == .poweredOff {
                self?.valid = false
            }
        }
    }
    
    deinit {
        NotificationCenter.default.removeObserver(self)
    }
    
    fileprivate func postPeripheralEvent(_ event: Notification.Name, userInfo: [AnyHashable: Any]?) {
        guard let peripheral = self.peripheral else {
            return
        }
        
        NotificationCenter.default.post(
            name: event,
            object: peripheral,
            userInfo: userInfo
        )
    }
}

// MARK: Connect/Disconnect Requests
extension PeripheralProxy {
    func connect(timeout: TimeInterval = 10, _ completion: @escaping ConnectPeripheralCallback) {
        if self.valid {
            Central.sharedInstance.connect(peripheral: self.cbPeripheral, timeout: timeout, completion: completion)
        } else {
            completion(.failure(SBError.invalidPeripheral))
        }
    }
    
    func disconnect(_ completion: @escaping DisconnectPeripheralCallback) {
        Central.sharedInstance.disconnect(peripheral: self.cbPeripheral, completion: completion)
    }
}

// MARK: RSSI Requests
private final class ReadRSSIRequest {
    let callback: ReadRSSIRequestCallback
    
    init(callback: @escaping ReadRSSIRequestCallback) {
        self.callback = callback
    }
}

extension PeripheralProxy {
    func readRSSI(_ completion: @escaping ReadRSSIRequestCallback) {
        self.connect { (result) in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            let request = ReadRSSIRequest(callback: completion)
            
            self.readRSSIRequests.append(request)
            
            if self.readRSSIRequests.count == 1 {
                self.runRSSIRequest()
            }
        }
    }
    
    fileprivate func runRSSIRequest() {
        guard let request = self.readRSSIRequests.first else {
            return
        }
        
        self.cbPeripheral.readRSSI()
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onReadRSSIOperationTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onReadRSSIOperationTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<ReadRSSIRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        self.readRSSIRequests.removeFirst()
        
        request.callback(.failure(SBError.operationTimedOut(operation: .readRSSI)))
        
        self.runRSSIRequest()
    }
}

// MARK: Service requests
private final class ServiceRequest {
    let serviceUUIDs: [CBUUID]?
    
    let callback: ServiceRequestCallback
    
    init(serviceUUIDs: [CBUUID]?, callback: @escaping ServiceRequestCallback) {
        self.callback = callback
        
        if let serviceUUIDs = serviceUUIDs {
            self.serviceUUIDs = serviceUUIDs
        } else {
            self.serviceUUIDs = nil
        }
    }
}

extension PeripheralProxy {
    func discoverServices(_ serviceUUIDs: [CBUUID]?, completion: @escaping ServiceRequestCallback) {
        self.connect { (result) in
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // Checking if the peripheral has already discovered the services requested
            if let serviceUUIDs = serviceUUIDs {
                let servicesTuple = self.cbPeripheral.servicesWithUUIDs(serviceUUIDs)
                
                if servicesTuple.missingServicesUUIDs.count == 0 {
                    completion(.success(servicesTuple.foundServices))
                    return
                }
            }
            
            let request = ServiceRequest(serviceUUIDs: serviceUUIDs) { result in
                completion(result)
            }
            
            self.serviceRequests.append(request)
            
            if self.serviceRequests.count == 1 {
                self.runServiceRequest()
            }
        }
    }
    
    fileprivate func runServiceRequest() {
        guard let request = self.serviceRequests.first else {
            return
        }
        
        self.cbPeripheral.discoverServices(request.serviceUUIDs)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onServiceRequestTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onServiceRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<ServiceRequest>
        
        // If the original rssi read operation callback is still there, this should mean the operation went
        // through and this timer can be ignored
        guard let request = weakRequest.value else {
            return
        }
        
        self.serviceRequests.removeFirst()
        
        request.callback(.failure(SBError.operationTimedOut(operation: .discoverServices)))
        
        self.runServiceRequest()
    }
}

// MARK: Included services Request
private final class IncludedServicesRequest {
    let serviceUUIDs: [CBUUID]?
    let parentService: CBService
    
    let callback: ServiceRequestCallback
    
    init(serviceUUIDs: [CBUUID]?, forService service: CBService, callback: @escaping ServiceRequestCallback) {
        self.callback = callback
        
        if let serviceUUIDs = serviceUUIDs {
            self.serviceUUIDs = serviceUUIDs
        } else {
            self.serviceUUIDs = nil
        }
        
        self.parentService = service
    }
}

extension PeripheralProxy {
    func discoverIncludedServices(_ serviceUUIDs: [CBUUID]?, forService serviceUUID: CBUUID, completion: @escaping ServiceRequestCallback) {
        self.discoverServices([serviceUUID]) { result in
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            let parentService = result.value!.first!
            
            let request = IncludedServicesRequest(serviceUUIDs: serviceUUIDs, forService: parentService) { result in
                completion(result)
            }
            
            self.includedServicesRequests.append(request)
            
            if self.includedServicesRequests.count == 1 {
                self.runIncludedServicesRequest()
            }
        }
    }
    
    fileprivate func runIncludedServicesRequest() {
        guard let request = self.includedServicesRequests.first else {
            return
        }
        
        self.cbPeripheral.discoverIncludedServices(request.serviceUUIDs, for: request.parentService)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onIncludedServicesRequestTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onIncludedServicesRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<IncludedServicesRequest>
        
        // If the original discover included services callback is still there, this means the operation went
        // through and this timer can be ignored
        guard let request = weakRequest.value else {
            return
        }
        
        self.includedServicesRequests.removeFirst()
        
        request.callback(.failure(SBError.operationTimedOut(operation: .discoverIncludedServices)))
        
        self.runIncludedServicesRequest()
    }
}

// MARK: Discover Characteristic requests
private final class CharacteristicRequest{
    let service: CBService
    let characteristicUUIDs: [CBUUID]?
    
    let callback: CharacteristicRequestCallback
    
    init(service: CBService,
         characteristicUUIDs: [CBUUID]?,
         callback: @escaping CharacteristicRequestCallback)
    {
        self.callback = callback
        
        self.service = service
        
        if let characteristicUUIDs = characteristicUUIDs {
            self.characteristicUUIDs = characteristicUUIDs
        } else {
            self.characteristicUUIDs = nil
        }
    }
    
}

extension PeripheralProxy {
    func discoverCharacteristics(_ characteristicUUIDs: [CBUUID]?,
                                 forService serviceUUID: CBUUID,
                                            completion: @escaping CharacteristicRequestCallback)
    {
        self.discoverServices([serviceUUID]) { result in
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // It would be a bug if we received an empty service array without an error from the discoverServices function
            // when asking for a specific service
            let service = result.value!.first!
            
            // Checking if this service already has the characteristic requested
            if let characteristicUUIDs = characteristicUUIDs {
                let characTuple = service.characteristicsWithUUIDs(characteristicUUIDs)
                
                if (characTuple.missingCharacteristicsUUIDs.count == 0) {
                    completion(.success(characTuple.foundCharacteristics))
                    return
                }
            }
            
            let request = CharacteristicRequest(service: service,
                                                characteristicUUIDs: characteristicUUIDs) { result in
                completion(result)
            }
            
            self.characteristicRequests.append(request)
            
            if self.characteristicRequests.count == 1 {
                self.runCharacteristicRequest()
            }
        }
    }
    
    fileprivate func runCharacteristicRequest() {
        guard let request = self.characteristicRequests.first else {
            return
        }
        
        self.cbPeripheral.discoverCharacteristics(request.characteristicUUIDs, for: request.service)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onCharacteristicRequestTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onCharacteristicRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<CharacteristicRequest>
        
        // If the original rssi read operation callback is still there, this should mean the operation went
        // through and this timer can be ignored
        guard let request = weakRequest.value else {
            return
        }
        
        self.characteristicRequests.removeFirst()
        
        request.callback(.failure(SBError.operationTimedOut(operation: .discoverCharacteristics)))
        
        self.runCharacteristicRequest()
    }
}

// MARK: Discover Descriptors requets
private final class DescriptorRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    
    let callback: DescriptorRequestCallback
    
    init(characteristic: CBCharacteristic, callback: @escaping DescriptorRequestCallback) {
        self.callback = callback
        
        self.service = characteristic.service!
        self.characteristic = characteristic
    }
    
}

extension PeripheralProxy {
    func discoverDescriptorsForCharacteristic(_ characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping DescriptorRequestCallback) {
        self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // It would be a terrible bug in the first place if the discover characteristic returned an empty array
            // with no error message when searching for a specific characteristic, I want to crash if it happens :)
            let characteristic = result.value!.first!
            
            let request = DescriptorRequest(characteristic: characteristic) { result in
                completion(result)
            }
            
            self.descriptorRequests.append(request)
            
            if self.descriptorRequests.count == 1 {
                self.runDescriptorRequest()
            }
        }
    }
    
    fileprivate func runDescriptorRequest() {
        guard let request = self.descriptorRequests.first else {
            return
        }
        
        self.cbPeripheral.discoverDescriptors(for: request.characteristic)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onDescriptorRequestTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onDescriptorRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<DescriptorRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        self.descriptorRequests.removeFirst()
        
        request.callback(.failure(SBError.operationTimedOut(operation: .discoverDescriptors)))
        
        self.runDescriptorRequest()
    }
}

// MARK: Read Characteristic value requests
private final class ReadCharacteristicRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    
    let callback: ReadCharacRequestCallback
    
    init(characteristic: CBCharacteristic, callback: @escaping ReadCharacRequestCallback) {
        self.callback = callback
        
        self.service = characteristic.service!
        self.characteristic = characteristic
    }
    
}

extension PeripheralProxy {
    func readCharacteristic(_ characteristicUUID: CBUUID,
                            serviceUUID: CBUUID,
                            completion: @escaping ReadCharacRequestCallback) {
        
        self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // Having no error yet not having the characteristic should never happen and would be considered a bug,
            // I'd rather crash here than not notice the bug
            let characteristic = result.value!.first!
            
            let request = ReadCharacteristicRequest(characteristic: characteristic) { result in
                completion(result)
            }
            
            let readPath = characteristic.uuidPath
            
            if var currentPathRequests = self.readCharacteristicRequests[readPath] {
                currentPathRequests.append(request)
                self.readCharacteristicRequests[readPath] = currentPathRequests
            } else {
                self.readCharacteristicRequests[readPath] = [request]
                
                self.runReadCharacteristicRequest(readPath)
            }
        }
    }
    
    fileprivate func runReadCharacteristicRequest(_ readPath: CBUUIDPath) {
        guard let request = self.readCharacteristicRequests[readPath]?.first else {
            return
        }
        
        self.cbPeripheral.readValue(for: request.characteristic)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onReadCharacteristicTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onReadCharacteristicTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<ReadCharacteristicRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        let readPath = request.characteristic.uuidPath
        
        self.readCharacteristicRequests[readPath]?.removeFirst()
        if self.readCharacteristicRequests[readPath]?.count == 0 {
            self.readCharacteristicRequests[readPath] = nil
        }
        
        request.callback(.failure(SBError.operationTimedOut(operation: .readCharacteristic)))
        
        self.runReadCharacteristicRequest(readPath)
    }
}

// MARK: Read Descriptor value requests
private final class ReadDescriptorRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    let descriptor: CBDescriptor
    
    let callback: ReadDescriptorRequestCallback
    
    init(descriptor: CBDescriptor, callback: @escaping ReadDescriptorRequestCallback) {
        self.callback = callback
        
        self.descriptor = descriptor
        self.characteristic = descriptor.characteristic!
        self.service = descriptor.characteristic!.service!
    }
    
}

extension PeripheralProxy {
    func readDescriptor(_ descriptorUUID: CBUUID,
                        characteristicUUID: CBUUID,
                        serviceUUID: CBUUID,
                        completion: @escaping ReadDescriptorRequestCallback)
    {
        
        self.discoverDescriptorsForCharacteristic(characteristicUUID, serviceUUID: serviceUUID) { result in
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            guard let descriptor = result.value?.first else {
                completion(.failure(SBError.peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [descriptorUUID])))
                return
            }
            
            let request = ReadDescriptorRequest(descriptor: descriptor, callback: completion)
            
            let readPath = descriptor.uuidPath
            
            if var currentPathRequests = self.readDescriptorRequests[readPath] {
                currentPathRequests.append(request)
                self.readDescriptorRequests[readPath] = currentPathRequests
            } else {
                self.readDescriptorRequests[readPath] = [request]
                
                self.runReadDescriptorRequest(readPath)
            }
        }
    }
    
    fileprivate func runReadDescriptorRequest(_ readPath: CBUUIDPath) {
        guard let request = self.readDescriptorRequests[readPath]?.first else {
            return
        }
        
        self.cbPeripheral.readValue(for: request.descriptor)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onReadDescriptorTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onReadDescriptorTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<ReadDescriptorRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        let readPath = request.descriptor.uuidPath
        
        self.readDescriptorRequests[readPath]?.removeFirst()
        if self.readDescriptorRequests[readPath]?.count == 0 {
            self.readDescriptorRequests[readPath] = nil
        }
        
        request.callback(.failure(SBError.operationTimedOut(operation: .readDescriptor)))
        
        self.runReadDescriptorRequest(readPath)
    }
}

// MARK: Write Characteristic value requests
private final class WriteCharacteristicValueRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    let value: Data
    let type: CBCharacteristicWriteType
    
    let callback: WriteRequestCallback
    
    init(characteristic: CBCharacteristic, value: Data, type: CBCharacteristicWriteType, callback: @escaping WriteRequestCallback) {
        self.callback = callback
        self.value = value
        self.type = type
        self.characteristic = characteristic
        self.service = characteristic.service!
    }
    
}

extension PeripheralProxy {
    func writeCharacteristicValue(_ characteristicUUID: CBUUID,
                                  serviceUUID: CBUUID,
                                  value: Data,
                                  type: CBCharacteristicWriteType,
                                  completion: @escaping WriteRequestCallback)
    {
        self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // Having no error yet not having the characteristic should never happen and would be considered a bug,
            // I'd rather crash here than not notice the bug hence the forced unwrap
            let characteristic = result.value!.first!
            
            let request = WriteCharacteristicValueRequest(characteristic: characteristic,
                                                          value: value,
                                                          type: type) { result in
                completion(result)
            }
            
            let writePath = characteristic.uuidPath
            
            if var currentPathRequests = self.writeCharacteristicValueRequests[writePath] {
                currentPathRequests.append(request)
                self.writeCharacteristicValueRequests[writePath] = currentPathRequests
            } else {
                self.writeCharacteristicValueRequests[writePath] = [request]
                
                self.runWriteCharacteristicValueRequest(writePath)
            }
        }
    }
    
    fileprivate func runWriteCharacteristicValueRequest(_ writePath: CBUUIDPath) {
        guard let request = self.writeCharacteristicValueRequests[writePath]?.first else {
            return
        }
        
        self.cbPeripheral.writeValue(request.value, for: request.characteristic, type: request.type)
        
        if request.type == CBCharacteristicWriteType.withResponse {
            Timer.scheduledTimer(
                timeInterval: PeripheralProxy.defaultTimeoutInS,
                target: self,
                selector: #selector(self.onWriteCharacteristicValueRequestTimerTick),
                userInfo: Weak(value: request),
                repeats: false)
        } else {
            // If no response is expected, we execute the callback and clear the request right away
            self.writeCharacteristicValueRequests[writePath]?.removeFirst()
            if self.writeCharacteristicValueRequests[writePath]?.count == 0 {
                self.writeCharacteristicValueRequests[writePath] = nil
            }
            
            request.callback(.success(()))
            
            self.runWriteCharacteristicValueRequest(writePath)
        }
        
    }
    
    @objc fileprivate func onWriteCharacteristicValueRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<WriteCharacteristicValueRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        let writePath = request.characteristic.uuidPath
        
        self.writeCharacteristicValueRequests[writePath]?.removeFirst()
        if self.writeCharacteristicValueRequests[writePath]?.count == 0 {
            self.writeCharacteristicValueRequests[writePath] = nil
        }
        
        request.callback(.failure(SBError.operationTimedOut(operation: .writeCharacteristic)))
        
        self.runWriteCharacteristicValueRequest(writePath)
    }
}

// MARK: Write Descriptor value requests
private final class WriteDescriptorValueRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    let descriptor: CBDescriptor
    let value: Data
    
    let callback: WriteRequestCallback
    
    init(descriptor: CBDescriptor, value: Data, callback: @escaping WriteRequestCallback) {
        self.callback = callback
        self.value = value
        self.descriptor = descriptor
        self.characteristic = descriptor.characteristic!
        self.service = descriptor.characteristic!.service!
    }
}

extension PeripheralProxy {
    func writeDescriptorValue(_ descriptorUUID: CBUUID,
                              characteristicUUID: CBUUID,
                              serviceUUID: CBUUID,
                              value: Data,
                              completion: @escaping WriteRequestCallback)
    {
        self.discoverDescriptorsForCharacteristic(characteristicUUID, serviceUUID: serviceUUID) { result in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            guard let descriptor = result.value?.filter({ $0.uuid == descriptorUUID }).first else {
                completion(.failure(SBError.peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [descriptorUUID])))
                return
            }
            
            let request = WriteDescriptorValueRequest(descriptor: descriptor, value: value) { result in
                completion(result)
            }
            
            let writePath = descriptor.uuidPath
            
            if var currentPathRequests = self.writeDescriptorValueRequests[writePath] {
                currentPathRequests.append(request)
                self.writeDescriptorValueRequests[writePath] = currentPathRequests
            } else {
                self.writeDescriptorValueRequests[writePath] = [request]
                
                self.runWriteDescriptorValueRequest(writePath)
            }
        }
    }
    
    fileprivate func runWriteDescriptorValueRequest(_ writePath: CBUUIDPath) {
        guard let request = self.writeDescriptorValueRequests[writePath]?.first else {
            return
        }
        
        self.cbPeripheral.writeValue(request.value, for: request.descriptor)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onWriteDescriptorValueRequestTimerTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onWriteDescriptorValueRequestTimerTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<WriteDescriptorValueRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        let writePath = request.descriptor.uuidPath
        
        self.writeDescriptorValueRequests[writePath]?.removeFirst()
        if self.writeDescriptorValueRequests[writePath]?.count == 0 {
            self.writeDescriptorValueRequests[writePath] = nil
        }
        
        request.callback(.failure(SBError.operationTimedOut(operation: .writeDescriptor)))
        
        self.runWriteDescriptorValueRequest(writePath)
    }
}

// MARK: Update Characteristic Notification State requests
private final class UpdateNotificationStateRequest {
    let service: CBService
    let characteristic: CBCharacteristic
    let enabled: Bool
    
    let callback: UpdateNotificationStateCallback
    
    init(enabled: Bool, characteristic: CBCharacteristic, callback: @escaping UpdateNotificationStateCallback) {
        self.enabled = enabled
        self.characteristic = characteristic
        self.service = characteristic.service!
        self.callback = callback
    }
}

extension PeripheralProxy {
    func setNotifyValueForCharacteristic(_ enabled: Bool, characteristicUUID: CBUUID, serviceUUID: CBUUID, completion: @escaping UpdateNotificationStateCallback) {
        
        self.discoverCharacteristics([characteristicUUID], forService: serviceUUID) { result in
            
            if let error = result.error {
                completion(.failure(error))
                return
            }
            
            // Having no error yet not having the characteristic should never happen and would be considered a bug,
            // I'd rather crash here than not notice the bug hence the forced unwrap
            let characteristic = result.value!.first!
            
            let request = UpdateNotificationStateRequest(enabled: enabled,
                                                         characteristic: characteristic) { result in
                completion(result)
            }
            
            let path = characteristic.uuidPath
            
            if var currentPathRequests = self.updateNotificationStateRequests[path] {
                currentPathRequests.append(request)
                self.updateNotificationStateRequests[path] = currentPathRequests
            } else {
                self.updateNotificationStateRequests[path] = [request]
                
                self.runUpdateNotificationStateRequest(path)
            }
        }
    }
    
    fileprivate func runUpdateNotificationStateRequest(_ path: CBUUIDPath) {
        guard let request = self.updateNotificationStateRequests[path]?.first else {
            return
        }
        
        self.cbPeripheral.setNotifyValue(request.enabled, for: request.characteristic)
        
        Timer.scheduledTimer(
            timeInterval: PeripheralProxy.defaultTimeoutInS,
            target: self,
            selector: #selector(self.onUpdateNotificationStateRequestTick),
            userInfo: Weak(value: request),
            repeats: false)
    }
    
    @objc fileprivate func onUpdateNotificationStateRequestTick(_ timer: Timer) {
        defer {
            if timer.isValid { timer.invalidate() }
        }
        
        let weakRequest = timer.userInfo as! Weak<UpdateNotificationStateRequest>
        
        guard let request = weakRequest.value else {
            return
        }
        
        let path = request.characteristic.uuidPath
        
        self.updateNotificationStateRequests[path]?.removeFirst()
        if self.updateNotificationStateRequests[path]?.count == 0 {
            self.updateNotificationStateRequests[path] = nil
        }
        
        request.callback(.failure(SBError.operationTimedOut(operation: .updateNotificationStatus)))
        
        self.runUpdateNotificationStateRequest(path)
    }
}

// MARK: CBPeripheralDelegate
extension PeripheralProxy: CBPeripheralDelegate {
    
    @objc func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
        guard let readRSSIRequest = self.readRSSIRequests.first else {
            return
        }
        
        self.readRSSIRequests.removeFirst()
        
        let result: Result<Int, Error> = {
            if let error = error {
                return .failure(error)
            } else {
                return .success(RSSI.intValue)
            }
        }()
        
        readRSSIRequest.callback(result)
        
        self.runRSSIRequest()
    }
    
    @objc func peripheralDidUpdateName(_ peripheral: CBPeripheral) {
        var userInfo: [AnyHashable: Any]?
        if let name = peripheral.name {
            userInfo = ["name": name]
        }
        
        self.postPeripheralEvent(Peripheral.PeripheralNameUpdate, userInfo: userInfo)
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didModifyServices invalidatedServices: [CBService]) {
        self.postPeripheralEvent(Peripheral.PeripheralModifedServices, userInfo: ["invalidatedServices": invalidatedServices])
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverIncludedServicesFor service: CBService, error: Error?) {
        guard let includedServicesRequest = self.includedServicesRequests.first else {
            return
        }
        
        defer {
            self.runIncludedServicesRequest()
        }
        
        self.includedServicesRequests.removeFirst()
        
        if let error = error {
            includedServicesRequest.callback(.failure(error))
            return
        }
        
        if let serviceUUIDs = includedServicesRequest.serviceUUIDs {
            let servicesTuple = peripheral.servicesWithUUIDs(serviceUUIDs)
            if servicesTuple.missingServicesUUIDs.count > 0 {
                includedServicesRequest.callback(.failure(SBError.peripheralServiceNotFound(missingServicesUUIDs: servicesTuple.missingServicesUUIDs)))
            } else { // This implies that all the services we're found through Set logic in the servicesWithUUIDs function
                includedServicesRequest.callback(.success(servicesTuple.foundServices))
            }
        } else {
            includedServicesRequest.callback(.success(service.includedServices ?? []))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
        guard let serviceRequest = self.serviceRequests.first else {
            return
        }
        
        defer {
            self.runServiceRequest()
        }
        
        self.serviceRequests.removeFirst()
        
        if let error = error {
            serviceRequest.callback(.failure(error))
            return
        }
        
        if let serviceUUIDs = serviceRequest.serviceUUIDs {
            let servicesTuple = peripheral.servicesWithUUIDs(serviceUUIDs)
            if servicesTuple.missingServicesUUIDs.count > 0 {
                serviceRequest.callback(.failure(SBError.peripheralServiceNotFound(missingServicesUUIDs: servicesTuple.missingServicesUUIDs)))
            } else { // This implies that all the services we're found through Set logic in the servicesWithUUIDs function
                serviceRequest.callback(.success(servicesTuple.foundServices))
            }
        } else {
            serviceRequest.callback(.success(peripheral.services ?? []))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
        guard let characteristicRequest = self.characteristicRequests.first else {
            return
        }
        
        defer {
            self.runCharacteristicRequest()
        }
        
        self.characteristicRequests.removeFirst()
        
        if let error = error {
            characteristicRequest.callback(.failure(error))
            return
        }
        
        if let characteristicUUIDs = characteristicRequest.characteristicUUIDs {
            let characteristicsTuple = service.characteristicsWithUUIDs(characteristicUUIDs)
            
            if characteristicsTuple.missingCharacteristicsUUIDs.count > 0 {
                characteristicRequest.callback(.failure(SBError.peripheralCharacteristicNotFound(missingCharacteristicsUUIDs: characteristicsTuple.missingCharacteristicsUUIDs)))
            } else {
                characteristicRequest.callback(.success(characteristicsTuple.foundCharacteristics))
            }
        } else {
            characteristicRequest.callback(.success(service.characteristics ?? []))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didDiscoverDescriptorsFor characteristic: CBCharacteristic, error: Error?) {
        guard let descriptorRequest = self.descriptorRequests.first else {
            return
        }
        
        defer {
            self.runDescriptorRequest()
        }
        
        self.descriptorRequests.removeFirst()
        
        if let error = error {
            descriptorRequest.callback(.failure(error))
        } else {
            descriptorRequest.callback(.success(characteristic.descriptors ?? []))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
        let readPath = characteristic.uuidPath
        
        guard let request = self.readCharacteristicRequests[readPath]?.first else {
            if characteristic.isNotifying {
                var userInfo: [AnyHashable: Any] = ["characteristic": characteristic]
                if let error = error {
                    userInfo["error"] = error
                }
                
                self.postPeripheralEvent(Peripheral.PeripheralCharacteristicValueUpdate, userInfo: userInfo)
            }
            return
        }
        
        defer {
            self.runReadCharacteristicRequest(readPath)
        }
        
        self.readCharacteristicRequests[readPath]?.removeFirst()
        if self.readCharacteristicRequests[readPath]?.count == 0 {
            self.readCharacteristicRequests[readPath] = nil
        }
        
        if let error = error {
            request.callback(.failure(error))
        } else {
            request.callback(.success(characteristic.value!))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
        let writePath = characteristic.uuidPath
        
        guard let request = self.writeCharacteristicValueRequests[writePath]?.first else {
            return
        }
        
        defer {
            self.runWriteCharacteristicValueRequest(writePath)
        }
        
        self.writeCharacteristicValueRequests[writePath]?.removeFirst()
        if self.writeCharacteristicValueRequests[writePath]?.count == 0 {
            self.writeCharacteristicValueRequests[writePath] = nil
        }
        
        if let error = error {
            request.callback(.failure(error))
        } else {
            request.callback(.success(()))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didUpdateNotificationStateFor characteristic: CBCharacteristic, error: Error?) {
        let path = characteristic.uuidPath
        
        guard let request = self.updateNotificationStateRequests[path]?.first else {
            return
        }
        
        defer {
            self.runUpdateNotificationStateRequest(path)
        }
        
        self.updateNotificationStateRequests[path]?.removeFirst()
        if self.updateNotificationStateRequests[path]?.count == 0 {
            self.updateNotificationStateRequests[path] = nil
        }
        
        if let error = error {
            request.callback(.failure(error))
        } else {
            request.callback(.success(characteristic.isNotifying))
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor descriptor: CBDescriptor, error: Error?) {
        let readPath = descriptor.uuidPath
        
        guard let request = self.readDescriptorRequests[readPath]?.first else {
            return
        }
        
        defer {
            self.runReadCharacteristicRequest(readPath)
        }
        
        self.readDescriptorRequests[readPath]?.removeFirst()
        if self.readDescriptorRequests[readPath]?.count == 0 {
            self.readDescriptorRequests[readPath] = nil
        }
        
        if let error = error {
            request.callback(.failure(error))
        } else {
            do {
                let value = try DescriptorValue(descriptor: descriptor)
                request.callback(.success(value))
            } catch let error {
                request.callback(.failure(error))
            }
        }
    }
    
    @objc func peripheral(_ peripheral: CBPeripheral, didWriteValueFor descriptor: CBDescriptor, error: Error?) {
        let writePath = descriptor.uuidPath
        
        guard let request = self.writeDescriptorValueRequests[writePath]?.first else {
            return
        }
        
        defer {
            self.runWriteDescriptorValueRequest(writePath)
        }
        
        self.writeDescriptorValueRequests[writePath]?.removeFirst()
        if self.writeDescriptorValueRequests[writePath]?.count == 0 {
            self.writeDescriptorValueRequests[writePath] = nil
        }
        
        if let error = error {
            request.callback(.failure(error))
        } else {
            request.callback(.success(()))
        }
    }
}


================================================
FILE: Sources/SBError.swift
================================================
//
//  SBError.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

public enum SBError: Error {
    
    public enum SBBluetoothUnavailbleFailureReason {
        case unsupported
        case unauthorized
        case poweredOff
        case unknown
    }
    
    public enum SBOperation: String {
        case connectPeripheral = "Connect peripheral"
        case disconnectPeripheral = "Disconnect peripheral"
        case readRSSI = "Read RSSI"
        case discoverServices = "Discover services"
        case discoverIncludedServices = "Discover included services"
        case discoverCharacteristics = "Discover characteristics"
        case discoverDescriptors = "Discover descriptors"
        case readCharacteristic = "Read characteristic"
        case readDescriptor = "Read descriptor"
        case writeCharacteristic = "Write characteristic"
        case writeDescriptor = "Write descriptor"
        case updateNotificationStatus = "Update notification status"
    }
    
    case bluetoothUnavailable(reason: SBBluetoothUnavailbleFailureReason)
    case scanningEndedUnexpectedly
    case operationTimedOut(operation: SBOperation)
    case invalidPeripheral
    case peripheralFailedToConnectReasonUnknown
    case peripheralServiceNotFound(missingServicesUUIDs: [CBUUID])
    case peripheralCharacteristicNotFound(missingCharacteristicsUUIDs: [CBUUID])
    case peripheralDescriptorsNotFound(missingDescriptorsUUIDs: [CBUUID])
    case invalidDescriptorValue(descriptor: CBDescriptor)
}

extension SBError: LocalizedError {
    public var errorDescription: String? {
        switch self {
        case .bluetoothUnavailable(let reason):
            return reason.localizedDescription
        case .scanningEndedUnexpectedly:
            return "Your peripheral scan ended unexpectedly."
        case .operationTimedOut(let operation):
            return "Bluetooth operation timed out: \(operation.rawValue)"
        case .invalidPeripheral:
            return "Invalid Bluetooth peripheral, you must rediscover this peripheral to use it again."
        case .peripheralFailedToConnectReasonUnknown:
            return "Failed to connect to your peripheral for an unknown reason."
        case .peripheralServiceNotFound(let missingUUIDs):
            let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",")
            return "Peripheral service(s) not found: \(missingUUIDsString)"
        case .peripheralCharacteristicNotFound(let missingUUIDs):
            let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",")
            return "Peripheral charac(s) not found: \(missingUUIDsString)"
        case .peripheralDescriptorsNotFound(let missingUUIDs):
            let missingUUIDsString = missingUUIDs.map { $0.uuidString }.joined(separator: ",")
            return "Peripheral descriptor(s) not found: \(missingUUIDsString)"
        case .invalidDescriptorValue(let descriptor):
            return "Failed to parse value for descriptor with uuid: \(descriptor.uuid.uuidString)"
        }
    }
}

extension SBError.SBBluetoothUnavailbleFailureReason {
    public var localizedDescription: String {
        switch self {
        case .unsupported:
            return "Your iOS device does not support Bluetooth."
        case .unauthorized:
            return "Unauthorized to use Bluetooth."
        case .poweredOff:
            return "Bluetooth is disabled, enable bluetooth and try again."
        case .unknown:
            return "Bluetooth is currently unavailable (unknown reason)."
        }
    }
}


================================================
FILE: Sources/SwiftyBluetooth.h
================================================
//
//  SwiftyBluetooth.h
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

#import <UIKit/UIKit.h>

//! Project version number for SwiftyBluetooth.
FOUNDATION_EXPORT double SwiftyBluetoothVersionNumber;

//! Project version string for SwiftyBluetooth.
FOUNDATION_EXPORT const unsigned char SwiftyBluetoothVersionString[];

// In this header, you should import all the public headers of your framework using statements like #import <SwiftyBluetooth/PublicHeader.h>




================================================
FILE: Sources/SwiftyBluetooth.swift
================================================
//
//  SwiftyBluetooth.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import CoreBluetooth

// MARK: Shorthands for the Central singleton instance interface

/// Allows you to initially set the Central sharedInstance and use the restore
/// identifier string of your choice for state preservation between app
/// launches. Must be called before anything else from the library and can only be called once.
@discardableResult
public func setSharedCentralInstanceWith(restoreIdentifier: String) -> Central {
    return Central.setSharedInstanceWith(restoreIdentifier: restoreIdentifier)
}

/// Scans for Peripherals through a CBCentralManager scanForPeripheralsWithServices(...) function call.
///
/// - Parameter timeout: The scanning time in seconds before the scan is stopped and the completion closure is called with a scanStopped result.
/// - Parameter serviceUUIDs: The service UUIDs to search peripherals for or nil if looking for all peripherals.
/// - Parameter completion: The closures, called multiple times throughout a scan.
public func scanForPeripherals(withServiceUUIDs serviceUUIDs: [CBUUIDConvertible]? = nil,
                               options: [String : Any]? = nil,
                               timeoutAfter timeout: TimeInterval,
                               completion: @escaping PeripheralScanCallback)
{
    Central.sharedInstance.scanForPeripherals(withServiceUUIDs: serviceUUIDs,
                                              options: options,
                                              timeoutAfter: timeout,
                                              completion: completion)
}

/// Will stop the current scan through a CBCentralManager stopScan() function call and invokes the completion
/// closures of the original scanWithTimeout function call with a scanStopped result containing an error if something went wrong.
public func stopScan() {
    Central.sharedInstance.stopScan()
}

/// Sometimes, the bluetooth state of your iOS Device/CBCentralManagerState is in an inbetween state of either
/// ".Unknown" or ".Reseting". This function will wait until the bluetooth state is stable and return a subset
/// of the CBCentralManager state value which does not includes these values in its completion closure.
public func asyncState(completion: @escaping AsyncCentralStateCallback) {
    Central.sharedInstance.asyncState(completion: completion)
}

/// The Central singleton CBCentralManager isScanning value
public var isScanning: Bool {
    return Central.sharedInstance.isScanning
}

/// Attempts to return the periperals from a list of identifier "UUID"s
public func retrievePeripherals(withUUIDs uuids: [UUID]) -> [Peripheral] {
    return Central.sharedInstance.retrievePeripherals(withUUIDs: uuids)
}

/// Attempts to return the connected peripheral having the specific service CBUUIDs
public func retrieveConnectedPeripherals(withServiceUUIDs uuids: [CBUUIDConvertible]) -> [Peripheral] {
    return Central.sharedInstance.retrieveConnectedPeripherals(withServiceUUIDs: uuids)
}


================================================
FILE: Sources/Util.swift
================================================
//
//  Util.swift
//
//  Copyright (c) 2016 Jordane Belanger
//
//  Permission is hereby granted, free of charge, to any person obtaining a copy
//  of this software and associated documentation files (the "Software"), to deal
//  in the Software without restriction, including without limitation the rights
//  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
//  copies of the Software, and to permit persons to whom the Software is
//  furnished to do so, subject to the following conditions:
//
//  The above copyright notice and this permission notice shall be included in all
//  copies or substantial portions of the Software.
//
//  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
//  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
//  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
//  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
//  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
//  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
//  SOFTWARE.

import Foundation

public final class Box<T> {
    let value: T
    
    init(value: T) {
        self.value = value
    }
}

final class Weak<T: AnyObject> {
    weak var value : T?
    
    init (value: T) {
        self.value = value
    }
}

extension UInt16 {
    init?(uncastedUnwrappedNSNumber: AnyObject?) {
        guard let numberValue = uncastedUnwrappedNSNumber as? NSNumber else {
            return nil
        }
        
        self = UInt16(truncatingIfNeeded: numberValue.intValue)
    }
}

extension Result {
    var value: Success? {
        return try? self.get()
    }
    
    var error: Error? {
        do {
            let _ = try self.get()
            return nil
        } catch {
            return error
        }
    }
}


================================================
FILE: SwiftyBluetooth.podspec
================================================
Pod::Spec.new do |s|
  s.name         = 'SwiftyBluetooth'
  s.version      = '3.1.0'
  s.license      =  'MIT'
  s.homepage     = 'https://github.com/jordanebelanger/SwiftyBluetooth'
  s.authors      = { 'Jordane Belanger' => 'jordane.belanger@gmail.com' }
  s.summary      = 'Fully featured closures based library for CoreBluetooth'
  s.source       = { :git => 'https://github.com/jordanebelanger/SwiftyBluetooth.git', :tag => s.version }
  s.source_files = 'Sources/*.swift'
  s.requires_arc = true
  s.ios.deployment_target = '10.0'
  s.osx.deployment_target  = '10.15'
  s.tvos.deployment_target  = '10.15'
end


================================================
FILE: SwiftyBluetooth.xcodeproj/project.pbxproj
================================================
// !$*UTF8*$!
{
	archiveVersion = 1;
	classes = {
	};
	objectVersion = 46;
	objects = {

/* Begin PBXBuildFile section */
		096409BD1CC610CD00ADD4D9 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */; };
		DBDCCA6825192D1900A1912A /* SwiftyBluetooth.h in Headers */ = {isa = PBXBuildFile; fileRef = DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */; settings = {ATTRIBUTES = (Public, ); }; };
		DBDCCA6925192D1900A1912A /* CentralProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5C25192D1900A1912A /* CentralProxy.swift */; };
		DBDCCA6A25192D1900A1912A /* Peripheral.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5D25192D1900A1912A /* Peripheral.swift */; };
		DBDCCA6B25192D1900A1912A /* CBUUIDConvertible.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */; };
		DBDCCA6C25192D1900A1912A /* Central.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA5F25192D1900A1912A /* Central.swift */; };
		DBDCCA6D25192D1900A1912A /* PeripheralProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */; };
		DBDCCA6E25192D1900A1912A /* SBError.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6125192D1900A1912A /* SBError.swift */; };
		DBDCCA6F25192D1900A1912A /* Util.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6225192D1900A1912A /* Util.swift */; };
		DBDCCA7025192D1900A1912A /* SwiftyBluetooth.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */; };
		DBDCCA7125192D1900A1912A /* CBUUIDPath.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */; };
		DBDCCA7225192D1900A1912A /* DescriptorValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6525192D1900A1912A /* DescriptorValue.swift */; };
		DBDCCA7325192D1900A1912A /* CBExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DBDCCA6625192D1900A1912A /* CBExtensions.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
		096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftyBluetooth.framework; sourceTree = BUILT_PRODUCTS_DIR; };
		096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; };
		09D5152E1D2DA8F900049B59 /* SwiftyBluetooth.podspec */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = SwiftyBluetooth.podspec; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
		09D515301D2DA90A00049B59 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = "<group>"; };
		09D515311D2DA90A00049B59 /* README.md */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = "<group>"; };
		DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SwiftyBluetooth.h; sourceTree = "<group>"; };
		DBDCCA5C25192D1900A1912A /* CentralProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CentralProxy.swift; sourceTree = "<group>"; };
		DBDCCA5D25192D1900A1912A /* Peripheral.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Peripheral.swift; sourceTree = "<group>"; };
		DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBUUIDConvertible.swift; sourceTree = "<group>"; };
		DBDCCA5F25192D1900A1912A /* Central.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Central.swift; sourceTree = "<group>"; };
		DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PeripheralProxy.swift; sourceTree = "<group>"; };
		DBDCCA6125192D1900A1912A /* SBError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SBError.swift; sourceTree = "<group>"; };
		DBDCCA6225192D1900A1912A /* Util.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Util.swift; sourceTree = "<group>"; };
		DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftyBluetooth.swift; sourceTree = "<group>"; };
		DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBUUIDPath.swift; sourceTree = "<group>"; };
		DBDCCA6525192D1900A1912A /* DescriptorValue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DescriptorValue.swift; sourceTree = "<group>"; };
		DBDCCA6625192D1900A1912A /* CBExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CBExtensions.swift; sourceTree = "<group>"; };
		DBDCCA6725192D1900A1912A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
		096409AA1CC6103D00ADD4D9 /* Frameworks */ = {
			isa = PBXFrameworksBuildPhase;
			buildActionMask = 2147483647;
			files = (
				096409BD1CC610CD00ADD4D9 /* CoreBluetooth.framework in Frameworks */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXFrameworksBuildPhase section */

/* Begin PBXGroup section */
		096409A41CC6103D00ADD4D9 = {
			isa = PBXGroup;
			children = (
				DBDCCA5A25192D1900A1912A /* Sources */,
				09D515301D2DA90A00049B59 /* LICENSE */,
				09D515311D2DA90A00049B59 /* README.md */,
				09D5152E1D2DA8F900049B59 /* SwiftyBluetooth.podspec */,
				096409BF1CC610F100ADD4D9 /* Frameworks */,
				096409AF1CC6103D00ADD4D9 /* Products */,
			);
			sourceTree = "<group>";
		};
		096409AF1CC6103D00ADD4D9 /* Products */ = {
			isa = PBXGroup;
			children = (
				096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */,
			);
			name = Products;
			sourceTree = "<group>";
		};
		096409BF1CC610F100ADD4D9 /* Frameworks */ = {
			isa = PBXGroup;
			children = (
				096409BC1CC610CD00ADD4D9 /* CoreBluetooth.framework */,
			);
			name = Frameworks;
			sourceTree = "<group>";
		};
		DBDCCA5A25192D1900A1912A /* Sources */ = {
			isa = PBXGroup;
			children = (
				DBDCCA5B25192D1900A1912A /* SwiftyBluetooth.h */,
				DBDCCA5C25192D1900A1912A /* CentralProxy.swift */,
				DBDCCA5D25192D1900A1912A /* Peripheral.swift */,
				DBDCCA5E25192D1900A1912A /* CBUUIDConvertible.swift */,
				DBDCCA5F25192D1900A1912A /* Central.swift */,
				DBDCCA6025192D1900A1912A /* PeripheralProxy.swift */,
				DBDCCA6125192D1900A1912A /* SBError.swift */,
				DBDCCA6225192D1900A1912A /* Util.swift */,
				DBDCCA6325192D1900A1912A /* SwiftyBluetooth.swift */,
				DBDCCA6425192D1900A1912A /* CBUUIDPath.swift */,
				DBDCCA6525192D1900A1912A /* DescriptorValue.swift */,
				DBDCCA6625192D1900A1912A /* CBExtensions.swift */,
				DBDCCA6725192D1900A1912A /* Info.plist */,
			);
			path = Sources;
			sourceTree = "<group>";
		};
/* End PBXGroup section */

/* Begin PBXHeadersBuildPhase section */
		096409AB1CC6103D00ADD4D9 /* Headers */ = {
			isa = PBXHeadersBuildPhase;
			buildActionMask = 2147483647;
			files = (
				DBDCCA6825192D1900A1912A /* SwiftyBluetooth.h in Headers */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXHeadersBuildPhase section */

/* Begin PBXNativeTarget section */
		096409AD1CC6103D00ADD4D9 /* SwiftyBluetooth */ = {
			isa = PBXNativeTarget;
			buildConfigurationList = 096409B61CC6103D00ADD4D9 /* Build configuration list for PBXNativeTarget "SwiftyBluetooth" */;
			buildPhases = (
				096409A91CC6103D00ADD4D9 /* Sources */,
				096409AA1CC6103D00ADD4D9 /* Frameworks */,
				096409AB1CC6103D00ADD4D9 /* Headers */,
				096409AC1CC6103D00ADD4D9 /* Resources */,
			);
			buildRules = (
			);
			dependencies = (
			);
			name = SwiftyBluetooth;
			productName = SwiftyBluetooth;
			productReference = 096409AE1CC6103D00ADD4D9 /* SwiftyBluetooth.framework */;
			productType = "com.apple.product-type.framework";
		};
/* End PBXNativeTarget section */

/* Begin PBXProject section */
		096409A51CC6103D00ADD4D9 /* Project object */ = {
			isa = PBXProject;
			attributes = {
				LastUpgradeCheck = 1310;
				TargetAttributes = {
					096409AD1CC6103D00ADD4D9 = {
						CreatedOnToolsVersion = 7.3;
						LastSwiftMigration = 1140;
					};
				};
			};
			buildConfigurationList = 096409A81CC6103D00ADD4D9 /* Build configuration list for PBXProject "SwiftyBluetooth" */;
			compatibilityVersion = "Xcode 3.2";
			developmentRegion = en;
			hasScannedForEncodings = 0;
			knownRegions = (
				en,
				Base,
			);
			mainGroup = 096409A41CC6103D00ADD4D9;
			productRefGroup = 096409AF1CC6103D00ADD4D9 /* Products */;
			projectDirPath = "";
			projectRoot = "";
			targets = (
				096409AD1CC6103D00ADD4D9 /* SwiftyBluetooth */,
			);
		};
/* End PBXProject section */

/* Begin PBXResourcesBuildPhase section */
		096409AC1CC6103D00ADD4D9 /* Resources */ = {
			isa = PBXResourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXResourcesBuildPhase section */

/* Begin PBXSourcesBuildPhase section */
		096409A91CC6103D00ADD4D9 /* Sources */ = {
			isa = PBXSourcesBuildPhase;
			buildActionMask = 2147483647;
			files = (
				DBDCCA6925192D1900A1912A /* CentralProxy.swift in Sources */,
				DBDCCA6E25192D1900A1912A /* SBError.swift in Sources */,
				DBDCCA7225192D1900A1912A /* DescriptorValue.swift in Sources */,
				DBDCCA6C25192D1900A1912A /* Central.swift in Sources */,
				DBDCCA6D25192D1900A1912A /* PeripheralProxy.swift in Sources */,
				DBDCCA7125192D1900A1912A /* CBUUIDPath.swift in Sources */,
				DBDCCA7025192D1900A1912A /* SwiftyBluetooth.swift in Sources */,
				DBDCCA6F25192D1900A1912A /* Util.swift in Sources */,
				DBDCCA6A25192D1900A1912A /* Peripheral.swift in Sources */,
				DBDCCA6B25192D1900A1912A /* CBUUIDConvertible.swift in Sources */,
				DBDCCA7325192D1900A1912A /* CBExtensions.swift in Sources */,
			);
			runOnlyForDeploymentPostprocessing = 0;
		};
/* End PBXSourcesBuildPhase section */

/* Begin XCBuildConfiguration section */
		096409B41CC6103D00ADD4D9 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
				COPY_PHASE_STRIP = NO;
				CURRENT_PROJECT_VERSION = 1;
				DEBUG_INFORMATION_FORMAT = dwarf;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				ENABLE_TESTABILITY = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_DYNAMIC_NO_PIC = NO;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_OPTIMIZATION_LEVEL = 0;
				GCC_PREPROCESSOR_DEFINITIONS = (
					"DEBUG=1",
					"$(inherited)",
				);
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				MTL_ENABLE_DEBUG_INFO = YES;
				ONLY_ACTIVE_ARCH = YES;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Debug;
		};
		096409B51CC6103D00ADD4D9 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				ALWAYS_SEARCH_USER_PATHS = NO;
				CLANG_ANALYZER_NONNULL = YES;
				CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
				CLANG_CXX_LIBRARY = "libc++";
				CLANG_ENABLE_MODULES = YES;
				CLANG_ENABLE_OBJC_ARC = YES;
				CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
				CLANG_WARN_BOOL_CONVERSION = YES;
				CLANG_WARN_COMMA = YES;
				CLANG_WARN_CONSTANT_CONVERSION = YES;
				CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
				CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
				CLANG_WARN_EMPTY_BODY = YES;
				CLANG_WARN_ENUM_CONVERSION = YES;
				CLANG_WARN_INFINITE_RECURSION = YES;
				CLANG_WARN_INT_CONVERSION = YES;
				CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
				CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
				CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
				CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
				CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
				CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
				CLANG_WARN_STRICT_PROTOTYPES = YES;
				CLANG_WARN_SUSPICIOUS_MOVE = YES;
				CLANG_WARN_UNREACHABLE_CODE = YES;
				CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
				COPY_PHASE_STRIP = NO;
				CURRENT_PROJECT_VERSION = 1;
				DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
				ENABLE_NS_ASSERTIONS = NO;
				ENABLE_STRICT_OBJC_MSGSEND = YES;
				GCC_C_LANGUAGE_STANDARD = gnu99;
				GCC_NO_COMMON_BLOCKS = YES;
				GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
				GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
				GCC_WARN_UNDECLARED_SELECTOR = YES;
				GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
				GCC_WARN_UNUSED_FUNCTION = YES;
				GCC_WARN_UNUSED_VARIABLE = YES;
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				MTL_ENABLE_DEBUG_INFO = NO;
				SDKROOT = iphoneos;
				SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
				SWIFT_VERSION = 5.0;
				TARGETED_DEVICE_FAMILY = "1,2";
				VALIDATE_PRODUCT = YES;
				VERSIONING_SYSTEM = "apple-generic";
				VERSION_INFO_PREFIX = "";
			};
			name = Release;
		};
		096409B71CC6103D00ADD4D9 /* Debug */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_MODULES = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
				DEFINES_MODULE = YES;
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = Sources/Info.plist;
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = com.jordanebelanger.SwiftyBluetooth;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SKIP_INSTALL = YES;
				SWIFT_OPTIMIZATION_LEVEL = "-Onone";
				SWIFT_SWIFT3_OBJC_INFERENCE = Default;
				SWIFT_VERSION = 5.0;
			};
			name = Debug;
		};
		096409B81CC6103D00ADD4D9 /* Release */ = {
			isa = XCBuildConfiguration;
			buildSettings = {
				CLANG_ENABLE_MODULES = YES;
				"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
				DEFINES_MODULE = YES;
				DYLIB_COMPATIBILITY_VERSION = 1;
				DYLIB_CURRENT_VERSION = 1;
				DYLIB_INSTALL_NAME_BASE = "@rpath";
				INFOPLIST_FILE = Sources/Info.plist;
				INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
				IPHONEOS_DEPLOYMENT_TARGET = 10.0;
				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
				PRODUCT_BUNDLE_IDENTIFIER = com.jordanebelanger.SwiftyBluetooth;
				PRODUCT_NAME = "$(TARGET_NAME)";
				SKIP_INSTALL = YES;
				SWIFT_SWIFT3_OBJC_INFERENCE = Default;
				SWIFT_VERSION = 5.0;
			};
			name = Release;
		};
/* End XCBuildConfiguration section */

/* Begin XCConfigurationList section */
		096409A81CC6103D00ADD4D9 /* Build configuration list for PBXProject "SwiftyBluetooth" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				096409B41CC6103D00ADD4D9 /* Debug */,
				096409B51CC6103D00ADD4D9 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
		096409B61CC6103D00ADD4D9 /* Build configuration list for PBXNativeTarget "SwiftyBluetooth" */ = {
			isa = XCConfigurationList;
			buildConfigurations = (
				096409B71CC6103D00ADD4D9 /* Debug */,
				096409B81CC6103D00ADD4D9 /* Release */,
			);
			defaultConfigurationIsVisible = 0;
			defaultConfigurationName = Release;
		};
/* End XCConfigurationList section */
	};
	rootObject = 096409A51CC6103D00ADD4D9 /* Project object */;
}


================================================
FILE: SwiftyBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
   version = "1.0">
   <FileRef
      location = "self:">
   </FileRef>
</Workspace>


================================================
FILE: SwiftyBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist
================================================
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>IDEDidComputeMac32BitWarning</key>
	<true/>
</dict>
</plist>


================================================
FILE: SwiftyBluetooth.xcodeproj/xcshareddata/xcschemes/SwiftyBluetooth.xcscheme
================================================
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
   LastUpgradeVersion = "1310"
   version = "1.3">
   <BuildAction
      parallelizeBuildables = "YES"
      buildImplicitDependencies = "YES">
      <BuildActionEntries>
         <BuildActionEntry
            buildForTesting = "YES"
            buildForRunning = "YES"
            buildForProfiling = "YES"
            buildForArchiving = "YES"
            buildForAnalyzing = "YES">
            <BuildableReference
               BuildableIdentifier = "primary"
               BlueprintIdentifier = "096409AD1CC6103D00ADD4D9"
               BuildableName = "SwiftyBluetooth.framework"
               BlueprintName = "SwiftyBluetooth"
               ReferencedContainer = "container:SwiftyBluetooth.xcodeproj">
            </BuildableReference>
         </BuildActionEntry>
      </BuildActionEntries>
   </BuildAction>
   <TestAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      shouldUseLaunchSchemeArgsEnv = "YES">
      <Testables>
      </Testables>
   </TestAction>
   <LaunchAction
      buildConfiguration = "Debug"
      selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
      selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
      launchStyle = "0"
      useCustomWorkingDirectory = "NO"
      ignoresPersistentStateOnLaunch = "NO"
      debugDocumentVersioning = "YES"
      debugServiceExtension = "internal"
      allowLocationSimulation = "NO">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "096409AD1CC6103D00ADD4D9"
            BuildableName = "SwiftyBluetooth.framework"
            BlueprintName = "SwiftyBluetooth"
            ReferencedContainer = "container:SwiftyBluetooth.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </LaunchAction>
   <ProfileAction
      buildConfiguration = "Release"
      shouldUseLaunchSchemeArgsEnv = "YES"
      savedToolIdentifier = ""
      useCustomWorkingDirectory = "NO"
      debugDocumentVersioning = "YES">
      <MacroExpansion>
         <BuildableReference
            BuildableIdentifier = "primary"
            BlueprintIdentifier = "096409AD1CC6103D00ADD4D9"
            BuildableName = "SwiftyBluetooth.framework"
            BlueprintName = "SwiftyBluetooth"
            ReferencedContainer = "container:SwiftyBluetooth.xcodeproj">
         </BuildableReference>
      </MacroExpansion>
   </ProfileAction>
   <AnalyzeAction
      buildConfiguration = "Debug">
   </AnalyzeAction>
   <ArchiveAction
      buildConfiguration = "Release"
      revealArchiveInOrganizer = "YES">
   </ArchiveAction>
</Scheme>
Download .txt
gitextract_tl8xnbwt/

├── .gitignore
├── LICENSE
├── Package.swift
├── README.md
├── Sources/
│   ├── CBExtensions.swift
│   ├── CBUUIDConvertible.swift
│   ├── CBUUIDPath.swift
│   ├── Central.swift
│   ├── CentralProxy.swift
│   ├── DescriptorValue.swift
│   ├── Info.plist
│   ├── Peripheral.swift
│   ├── PeripheralProxy.swift
│   ├── SBError.swift
│   ├── SwiftyBluetooth.h
│   ├── SwiftyBluetooth.swift
│   └── Util.swift
├── SwiftyBluetooth.podspec
└── SwiftyBluetooth.xcodeproj/
    ├── project.pbxproj
    ├── project.xcworkspace/
    │   ├── contents.xcworkspacedata
    │   └── xcshareddata/
    │       └── IDEWorkspaceChecks.plist
    └── xcshareddata/
        └── xcschemes/
            └── SwiftyBluetooth.xcscheme
Condensed preview — 22 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (163K chars).
[
  {
    "path": ".gitignore",
    "chars": 1440,
    "preview": "# Xcode\n#\n# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore\n\n"
  },
  {
    "path": "LICENSE",
    "chars": 1083,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Jordane Belanger\n\nPermission is hereby granted, free of charge, to any person "
  },
  {
    "path": "Package.swift",
    "chars": 396,
    "preview": "// swift-tools-version:5.5\nimport PackageDescription\n\nlet package = Package(\n    name: \"SwiftyBluetooth\",\n    platforms:"
  },
  {
    "path": "README.md",
    "chars": 8841,
    "preview": "# SwiftyBluetooth\nClosures based APIs for CoreBluetooth.\n\n## Features\n- Replace the delegate based interface with a clos"
  },
  {
    "path": "Sources/CBExtensions.swift",
    "chars": 5243,
    "preview": "//\n//  CBExtensions.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge"
  },
  {
    "path": "Sources/CBUUIDConvertible.swift",
    "chars": 2746,
    "preview": "//\n//  CBUUIDConvertible.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of c"
  },
  {
    "path": "Sources/CBUUIDPath.swift",
    "chars": 2718,
    "preview": "//\n//  CBUUIDPath.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge, "
  },
  {
    "path": "Sources/Central.swift",
    "chars": 10058,
    "preview": "//\n//  Central.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge, to "
  },
  {
    "path": "Sources/CentralProxy.swift",
    "chars": 14888,
    "preview": "//\n//  CentralProxy.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge"
  },
  {
    "path": "Sources/DescriptorValue.swift",
    "chars": 4756,
    "preview": "//\n//  DescriptorValue.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of cha"
  },
  {
    "path": "Sources/Info.plist",
    "chars": 808,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "Sources/Peripheral.swift",
    "chars": 24604,
    "preview": "//\n//  Peripheral.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge, "
  },
  {
    "path": "Sources/PeripheralProxy.swift",
    "chars": 44809,
    "preview": "//\n//  PeripheralProxy.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of cha"
  },
  {
    "path": "Sources/SBError.swift",
    "chars": 4694,
    "preview": "//\n//  SBError.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge, to "
  },
  {
    "path": "Sources/SwiftyBluetooth.h",
    "chars": 1550,
    "preview": "//\n//  SwiftyBluetooth.h\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge,"
  },
  {
    "path": "Sources/SwiftyBluetooth.swift",
    "chars": 4121,
    "preview": "//\n//  SwiftyBluetooth.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of cha"
  },
  {
    "path": "Sources/Util.swift",
    "chars": 1904,
    "preview": "//\n//  Util.swift\n//\n//  Copyright (c) 2016 Jordane Belanger\n//\n//  Permission is hereby granted, free of charge, to any"
  },
  {
    "path": "SwiftyBluetooth.podspec",
    "chars": 616,
    "preview": "Pod::Spec.new do |s|\n  s.name         = 'SwiftyBluetooth'\n  s.version      = '3.1.0'\n  s.license      =  'MIT'\n  s.homep"
  },
  {
    "path": "SwiftyBluetooth.xcodeproj/project.pbxproj",
    "chars": 17468,
    "preview": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 46;\n\tobjects = {\n\n/* Begin PBXBuildFile section *"
  },
  {
    "path": "SwiftyBluetooth.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "chars": 135,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef"
  },
  {
    "path": "SwiftyBluetooth.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "chars": 238,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/P"
  },
  {
    "path": "SwiftyBluetooth.xcodeproj/xcshareddata/xcschemes/SwiftyBluetooth.xcscheme",
    "chars": 2815,
    "preview": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1310\"\n   version = \"1.3\">\n   <BuildAction\n      "
  }
]

About this extraction

This page contains the full source code of the jordanebelanger/SwiftyBluetooth GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 22 files (152.3 KB), approximately 33.2k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!