         // 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<()>) -> ())?) {
+        let request = ATTReadBlobRequest(
+            handle: operation.handle,
+            offset: operation.offset
+        )
+        
+        let response = try await send(request, response: ATTReadBlobResponse.self)
+        try await readBlobResponse(response, operation: operation)
+    }
+    
+    private func writeAttribute(
+        _ handle: UInt16,
+        data: Data,
+        reliableWrites: Bool,
+        completion: ((GATTClientResponse<()>) -> ())?
+    ) async throws {
         
         // short value
-        if data.count <= Int(maximumTransmissionUnit.rawValue) - 3 { // ATT_MTU - 3
-            
+        let shortValueLength = await Int(maximumTransmissionUnit.rawValue) - 3 // ATT_MTU - 3
+        if data.count <= shortValueLength {
             if let completion = completion {
-                
-                writeAttributeValue(handle,
-                                    data: data,
-                                    completion: completion)
-                
+                try await writeAttributeValue(
+                    handle,
+                    data: data,
+                    completion: completion
+                )
             } else {
-                
-                writeAttributeCommand(handle,
-                                      data: data)
-            }
-            
+                try await 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)
+            try await writeLongAttributeValue(
+                handle,
+                data: data,
+                reliableWrites: reliableWrites,
+                completion: completion
+            )
+        }
+    }
+    
+    private func writeAttributeCommand(_ attribute: UInt16, data: Data) async throws {
+        let length = await Int(maximumTransmissionUnit.rawValue) - 3
+        let data = Data(data.prefix(length))
+        let command = ATTWriteCommand(handle: attribute, value: data)
+        try await send(command)
     }
     
     /// 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<()>) -> ()) {
+    private func writeAttributeValue(
+        _ attribute: UInt16,
+        data: Data,
+        completion: @escaping (GATTClientResponse<()>) -> ()
+    ) async throws {
+        let length = await Int(maximumTransmissionUnit.rawValue) - 3
+        let data = Data(data.prefix(length))
+        let request = ATTWriteRequest(handle: attribute, value: data)
+        let response = try await send(request, response: ATTWriteResponse.self)
+        writeResponse(response, completion: completion)
+    }
+    
+    private func writeLongAttributeValue(
+        _ attribute: UInt16,
+        data: Data,
+        reliableWrites: Bool = false,
+        completion: @escaping (GATTClientResponse<()>) -> ()
+    ) async throws {
         
         // 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.

    /// ![Image](https://github.com/PureSwift/Bluetooth/raw/master/Assets/ReadUsingCharacteristicUUID.png)
    public func readCharacteristics(
        using uuid: BluetoothUUID,
        handleRange: (start: UInt16, end: UInt16) = (.min, .max),
        completion: @escaping (GATTClientResponse<[UInt16: Data]>) -> ()
    ) {
        
        precondition(handleRange.start < handleRange.end)
        
        // The Attribute Protocol Read By Type Request is used to perform the sub-procedure.
        // The Attribute Type is set to the known characteristic UUID and the Starting Handle and Ending Handle parameters
        // shall be set to the range over which this read is to be performed. This is typically the handle range for the service in which the characteristic belongs.
        
        let pdu = ATTReadByTypeRequest(
            startHandle: handleRange.start,
            endHandle: handleRange.end,
            attributeType: uuid
        )
        
        let operation = ReadUsingUUIDOperation(uuid: uuid, completion: completion)
        
        send(pdu) { [unowned self] in
            self.readByTypeResponse($0, operation: operation)
        }
    }
    

    private func readBlobResponse(
        _ response: ATTResponse<ATTReadBlobResponse>,
        operation: ReadOperation
    ) async throws {
        
        // For each Read Blob Request a Read Blob Response is received with a portion of the Characteristic Value contained in the Part Attribute Value parameter.
        switch response {
