         }
     }
     
-    private func discoverCharacteristics(uuid: BluetoothUUID? = nil,
-                                         service: Service,
-                                         completion: @escaping (GATTClientResponse<[Characteristic]>) -> ()) {
-        
-        let attributeType = GATTUUID.characteristic
-        
-        let operation = DiscoveryOperation<Characteristic>(uuid: uuid,
-                                                           start: service.handle,
-                                                           end: service.end,
-                                                           type: attributeType,
-                                                           completion: completion)
-        
-        let pdu = ATTReadByTypeRequest(startHandle: service.handle,
-                                       endHandle: service.end,
-                                       attributeType: attributeType.uuid)
-        
-        send(pdu) { [unowned self] in self.readByTypeResponse($0, operation: operation) }
-    }
-    
-    private func discoverDescriptors(operation: DescriptorDiscoveryOperation) {
-        
+    private func discoverCharacteristics(
+        uuid: BluetoothUUID? = nil,
+        service: Service
+    ) async throws -> [Characteristic] {
+        return try await withCheckedThrowingContinuation() { continuation in
+            Task {
+                do {
+                    let attributeType = GATTUUID.characteristic
+                    let operation = DiscoveryOperation<Characteristic>(
+                        uuid: uuid,
+                        start: service.handle,
+                        end: service.end,
+                        type: attributeType
+                    ) { result in
+                        continuation.resume(with: result)
+                    }
+                    let request = ATTReadByTypeRequest(
+                        startHandle: service.handle,
+                        endHandle: service.end,
+                        attributeType: attributeType.uuid
+                    )
+                    let response = try await send(request, response: ATTReadByTypeResponse.self)
+                    try await readByTypeResponse(response, operation: operation)
+                }
+                catch {
+                    assert(error as? ATTErrorResponse == nil)
+                    continuation.resume(throwing: error)
+                }
+            }
+        }
+    }
+    
+    private func discoverDescriptors(operation: DescriptorDiscoveryOperation) async throws {
         assert(operation.start <= operation.end, "Invalid range")
-        
-        let pdu = ATTFindInformationRequest(startHandle: operation.start, endHandle: operation.end)
-        
-        send(pdu) { [unowned self] in self.findInformationResponse($0, operation: operation) }
+        let request = ATTFindInformationRequest(startHandle: operation.start, endHandle: operation.end)
+        let response = try await send(request, response: ATTFindInformationResponse.self)
+        try await findInformationResponse(response, operation: operation)
     }
     
     /// Read Characteristic Value

        // The offset for subsequent Read Blob Requests is the next octet that has yet to be read.
        // The Read Blob Request is repeated until the Read Blob Response’s Part Attribute Value parameter is shorter than (ATT_MTU-1).
        
        let pdu = ATTReadBlobRequest(handle: operation.handle,
                                     offset: operation.offset)
        
        send(pdu) { [unowned self] in self.readBlobResponse($0, operation: operation) }
    }
    
    private func writeAttribute(_ handle: UInt16,
                                data: Data,
                                reliableWrites: Bool,
                                completion: ((GATTClientResponse<()>) -> ())?) {
        
        // short value
        if data.count <= Int(maximumTransmissionUnit.rawValue) - 3 { // ATT_MTU - 3
            
            if let completion = completion {
                
                writeAttributeValue(handle,
                                    data: data,
                                    completion: completion)
                
            } else {
                
                writeAttributeCommand(handle,
                                      data: data)
            }
            
        } else {
            
            let completion = completion ?? { _ in }
            
            writeLongAttributeValue(handle,
                                    data: data,
                                    reliableWrites: reliableWrites,
                                    completion: completion)
        }
    }
    
    private func writeAttributeCommand(_ attribute: UInt16, data: Data) {
        
        let data = Data(data.prefix(Int(maximumTransmissionUnit.rawValue) - 3))
        
        let pdu = ATTWriteCommand(handle: attribute, value: data)
        
        send(pdu)
    }
    
    /// Write attribute request.
    private func writeAttributeValue(_ attribute: UInt16,
                                     data: Data,
                                     completion: @escaping (GATTClientResponse<()>) -> ()) {
        
        let data = Data(data.prefix(Int(maximumTransmissionUnit.rawValue) - 3))
        
        let pdu = ATTWriteRequest(handle: attribute, value: data)

        send(pdu) { [unowned self] in self.writeResponse($0, completion: completion) }
    }
    
    private func writeLongAttributeValue(_ attribute: UInt16,
                                         data: Data,
                                         reliableWrites: Bool = false,
                                         completion: @escaping (GATTClientResponse<()>) -> ()) {
        
        // The Attribute Protocol Prepare Write Request and Execute Write Request are used to perform this sub-procedure.
        // The Attribute Handle parameter shall be set to the Characteristic Value Handle of the Characteristic Value to be written.

    private func readResponse(
        _ response: ATTResponse<ATTReadResponse>,
        operation: ReadOperation
    ) async throws {
        
        // The Read Response only contains a Characteristic Value that is less than or equal to (ATT_MTU – 1) octets in length.
        // If the Characteristic Value is greater than (ATT_MTU – 1) octets in length, the Read Long Characteristic Value procedure
