 
 // MARK: - Private Supporting Types
 
-internal extension GATTClient {
-    
-    internal struct Cache {
-        
-        fileprivate(set) var services = [BluetoothUUID: CachedService]()
-        
-        mutating func update(_ newValue: [Service], isCompleteSet: Bool = true) {
-            
-            if isCompleteSet {
-                
-                // remove services that no longer exist on the server
-                self.services.keys
-                    .filter { uuid in newValue.contains(where: { $0.uuid == uuid }) }
-                    .forEach { self.services[$0] = nil }
-            }
-            
-            // update cache
-            newValue.forEach {
-                let newValue: CachedService
-                if var oldValue = self.services[$0.uuid] {
-                    oldValue.service = $0
-                    newValue = oldValue
-                } else {
-                    newValue = CachedService(service: $0, characteristics: [:])
-                }
-                self.services[$0.uuid] = newValue
-            }
-        }
-        
-        func cache <T> (for characteristic: Characteristic, update: (inout CachedCharacteristic) -> (T)) -> T {
-            
-            for service in self.services.values {
-                
-                for var characteristicCache in service.characteristics.values {
-                    
-                    guard characteristicCache.characteristic.handle == characteristic.handle,
-                        characteristicCache.characteristic.uuid == characteristic.uuid
-                        else { continue }
-                    
-                    return update(&characteristicCache)
-                }
-            }
-            
-            fatalError("Characteristic \(characteristic.uuid) not found")
-        }
-        
-        func endHandle(for characteristic: GATTClient.Characteristic) -> UInt16? {
-            
-            for service in services.values {
-                
-                let characteristics = Array(service.characteristics.values)
-                
-                for (index, characteristicCache) in characteristics.enumerated() {
-                    
-                    guard characteristicCache.characteristic.handle == characteristic.handle,
-                        characteristicCache.characteristic.uuid == characteristic.uuid
-                        else { continue }
-                    
-                    let nextIndex = index + 1
-                    
-                    let end: UInt16
-                    
-                    // get start handle of next characteristic
-                    if nextIndex < characteristics.count {
-                        
-                        let nextCharacteristic = characteristics[index]
-                        
-                        end = nextCharacteristic.characteristic.handle.declaration
-                        
-                    } else {
-                        
-                        // use service end handle
-                        end = service.service.end
-                    }
-                    
-                    return end
-                }
-            }
-            
-            return nil
-        }
-        
-        func descriptors(for characteristic: Characteristic) -> [Descriptor] {
-            
-            for service in self.services.values {
-                
-                for characteristicCache in service.characteristics.values {
-                    
-                    guard characteristicCache.characteristic.handle == characteristic.handle,
-                        characteristicCache.characteristic.uuid == characteristic.uuid
-                        else { continue }
-                    
-                    return Array(characteristicCache.descriptors.values)
-                }
-            }
-            
-            return []
-        }
-        
-        @discardableResult
-        mutating func update(_ newValue: [GATTClient.Descriptor], for characteristic: GATTClient.Characteristic) -> Bool {
-            
-            for (serviceUUID, service) in self.services {
-                
-                for (characteristicUUID, characteristicCache) in service.characteristics {
-                    
-                    guard characteristicCache.characteristic.handle == characteristic.handle,
-                        characteristicCache.characteristic.uuid == characteristic.uuid
-                        else { continue }
-                    
-                    // update cache
-                    self.services[serviceUUID]?.characteristics[characteristicUUID]?.update(newValue)
-                }
-            }
-            
-            return false
-        }
-    }
-}
-
-internal extension GATTClient.Cache {
-    
-    internal struct CachedService {
-        
-        fileprivate(set) var service: GATTClient.Service
-        
-        fileprivate(set) var characteristics = [BluetoothUUID: CachedCharacteristic]()
-        
-        mutating func update(_ newValue: [GATTClient.Characteristic], isCompleteSet: Bool = true) {
-            
-            // remove services that no longer exist on the server
-            if isCompleteSet {
-                
-                self.characteristics.keys
-                    .filter { uuid in newValue.contains(where: { $0.uuid == uuid }) }
-                    .forEach { self.characteristics[$0] = nil }
-            }
-            
-            // update cache
-            newValue.forEach {
-                let newValue: CachedCharacteristic
-                if var oldValue = self.characteristics[$0.uuid] {
-                    oldValue.characteristic = $0
-                    newValue = oldValue
-                } else {
-                    newValue = CachedCharacteristic($0)
-                }
-                self.characteristics[$0.uuid] = newValue
-            }
-        }
-    }
-    
-    internal struct CachedCharacteristic {
-        
-        typealias Descriptor = GATTClient.Descriptor
-        
-        fileprivate(set) var characteristic: GATTClient.Characteristic
-        
-        fileprivate(set) var descriptors = [BluetoothUUID: Descriptor]()
-        
-        fileprivate(set) var clientConfiguration = GATTClientCharacteristicConfiguration()
-        
-        fileprivate init(_ characteristic: GATTClient.Characteristic) {
-            
-            self.characteristic = characteristic
-        }
-        
-        mutating func update(_ newValue: [GATTClient.Descriptor]) {
-            
-            // remove services that no longer exist on the server
-            self.descriptors.keys
-                .filter { uuid in newValue.contains(where: { $0.uuid == uuid }) }
-                .forEach { self.descriptors[$0] = nil }
-            
-            // update cache
-            newValue.forEach { self.descriptors[$0.uuid] = $0 }
-        }
-    }
-}
-
 fileprivate final class DiscoveryOperation <T> {
     
     let uuid: BluetoothUUID?

        // The Attribute Type is set to the UUID for «Characteristic» and the Starting Handle and Ending Handle
        // parameters shall be set to the service handle range.
        
        discoverCharacteristics(uuid: uuid, service: service) { [unowned self] (response) in
            
            // update cache
            switch response {
            case .error:
                break
            case let .value(value):
                self.cache.services[service.uuid]?.update(value, isCompleteSet: false)
            }
            
            // call completion
            completion(response)
        }
    }
    
    /// Read Characteristic Value

        /// The Attribute Protocol Read By Group Type Request shall be used with 
        /// the Attribute Type parameter set to the UUID for «Primary Service». 
        /// The Starting Handle shall be set to 0x0001 and the Ending Handle shall be set to 0xFFFF.
        discoverServices(start: 0x0001, end: 0xFFFF, primary: true, completion: completion)
    }
    
    /// Discover Primary Service by Service UUID
