         
         // add queued write
         let preparedWrite = PreparedWrite(handle: pdu.handle, value: pdu.partValue, offset: pdu.offset)
-        
         preparedWrites.append(preparedWrite)
-        
         let response = ATTPrepareWriteResponse(handle: pdu.handle, offset: pdu.offset, partValue: pdu.partValue)
-        
-        respond(response)
-    }
-    
-    private func executeWriteRequest(_ pdu: ATTExecuteWriteRequest) {
-        
+        await respond(response)
+    }
+    
+    private func executeWriteRequest(_ pdu: ATTExecuteWriteRequest) async {
         let opcode = type(of: pdu).attributeOpcode
-        
         log?("Execute Write Request (\(pdu))")
-        
         let preparedWrites = self.preparedWrites
         self.preparedWrites = []
-        
         var newValues = [UInt16: Data]()
-        
         switch pdu {
-            
         case .cancel:
-            
             break // queue always cleared
-            
         case .write:
-            
             // validate
             for write in preparedWrites {
-                
                 let previousValue = newValues[write.handle] ?? Data()
-                
                 let newValue = previousValue + write.value
-                
                 // validate offset?
                 newValues[write.handle] = newValue
             }
-            
             // validate new values
             for (handle, newValue) in newValues {
-                
                 let attribute = database[handle: handle]
-                
                 // validate application errors with write callback
                 if let error = willWrite?(attribute.uuid, handle, attribute.value, newValue) {
-                    
-                    errorResponse(opcode, error, handle)
+                    await errorResponse(opcode, error, handle)
                     return
                 }
             }
-            
             // write new values
             for (handle, newValue) in newValues {
-                
                 database.write(newValue, forAttribute: handle)
             }
         }
         
-        respond(ATTExecuteWriteResponse())
-        
+        await respond(ATTExecuteWriteResponse())
         for handle in newValues.keys {
-            
-            didWriteAttribute(handle)
+            await didWriteAttribute(handle)
         }
     }
 }
 
 // MARK: - Supporting Types
 
-private extension GATTServer {
+internal extension GATTServer {
     
     struct PreparedWrite {
         

        }
    }
    
    private func handleReadRequest(opcode: ATTOpcode,
                                   handle: UInt16,
                                   offset: UInt16 = 0,
                                   isBlob: Bool = false) -> Data? {
        
        // no attributes
        guard database.attributes.isEmpty == false
            else { errorResponse(opcode, .invalidHandle, handle); return nil }
        
        // validate handle
        guard database.contains(handle: handle)
            else { errorResponse(opcode, .invalidHandle, handle); return nil }
        
        // get attribute
        let attribute = database[handle: handle]
        
        // validate permissions
        if let error = checkPermissions([.read, .readAuthentication, .readEncrypt], attribute) {
            
            errorResponse(opcode, error, handle)
            return nil
        }
        

    
    // MARK: Callbacks
    
    private func exchangeMTU(_ pdu: ATTMaximumTransmissionUnitRequest) async {
        let serverMTU = preferredMaximumTransmissionUnit.rawValue
        let finalMTU = ATTMaximumTransmissionUnit(server: serverMTU, client: pdu.clientMTU)
        // Respond with the server MTU (not final MTU)
        await connection.queue(ATTMaximumTransmissionUnitResponse(serverMTU: serverMTU))
        // Set MTU
        await connection.setMaximumTransmissionUnit(finalMTU)
        log?("MTU Exchange (\(pdu.clientMTU) -> \(serverMTU))")
    }
    
    private func readByGroupType(_ pdu: ATTReadByGroupTypeRequest) async {
        typealias AttributeData = ATTReadByGroupTypeResponse.AttributeData
        log?("Read by Group Type (\(pdu.startHandle) - \(pdu.endHandle))")
        // validate handles
        guard pdu.startHandle != 0 && pdu.endHandle != 0 else {
            await errorResponse(type(of: pdu).attributeOpcode, .invalidHandle)
            return
        }
        guard pdu.startHandle <= pdu.endHandle else {
            await errorResponse(type(of: pdu).attributeOpcode, .invalidHandle, pdu.startHandle)
            return
        }
        // GATT defines that only the Primary Service and Secondary Service group types
        // can be used for the "Read By Group Type" request. Return an error if any other group type is given.
        guard pdu.type == .primaryService || pdu.type == .secondaryService else {
            await errorResponse(type(of: pdu).attributeOpcode, .unsupportedGroupType, pdu.startHandle)
            return
        }
        let attributeData = database.readByGroupType(handle: (pdu.startHandle, pdu.endHandle), type: pdu.type)
        guard let firstAttribute = attributeData.first else {
            await errorResponse(type(of: pdu).attributeOpcode, .attributeNotFound, pdu.startHandle)
            return
        }
        
        let mtu = Int(await connection.maximumTransmissionUnit.rawValue)
        let valueLength = firstAttribute.value.count
        let response: ATTReadByGroupTypeResponse
        
        // truncate for MTU if first handle is too large
        if ATTReadByGroupTypeResponse([firstAttribute]).dataLength > mtu {
            let maxLength = min(min(mtu - 6, 251), valueLength)
            let truncatedAttribute = AttributeData(
                attributeHandle: firstAttribute.attributeHandle,
                endGroupHandle: firstAttribute.endGroupHandle,
                value: Data(firstAttribute.value.prefix(maxLength))
            )
            response = ATTReadByGroupTypeResponse([truncatedAttribute])
        } else {
            var count = 1
            // respond with results that are the same length
            if attributeData.count > 1 {
                for (index, attribute) in attributeData.suffix(from: 1).enumerated() {
                    let newCount = index + 1
                    guard attribute.value.count == valueLength,
                        ATTReadByGroupTypeResponse.dataLength(for: attributeData.prefix(newCount)) <= mtu
                        else { break }
                    count = newCount
                }
            }
            
            let limitedAttributes = Array(attributeData.prefix(count))
            response = ATTReadByGroupTypeResponse(limitedAttributes)
        }
        
        assert(response.dataLength <= mtu,
               "Response \(response.dataLength) bytes > MTU (\(mtu))")
        
        await respond(response)
    }
    
    private func readByType(_ pdu: ATTReadByTypeRequest) async {
        typealias AttributeData = ATTReadByTypeResponse.AttributeData
        log?("Read by Type (\(pdu.attributeType)) (\(pdu.startHandle) - \(pdu.endHandle))")
        guard pdu.startHandle != 0 && pdu.endHandle != 0
            else { await errorResponse(type(of: pdu).attributeOpcode, .invalidHandle); return }
        
        guard pdu.startHandle <= pdu.endHandle
            else { await errorResponse(type(of: pdu).attributeOpcode, .invalidHandle, pdu.startHandle); return }
        
        let attributeData = database
            .readByType(handle: (pdu.startHandle, pdu.endHandle), type: pdu.attributeType)
            .map { AttributeData(handle: $0.handle, value: $0.value) }
        
        guard let firstAttribute = attributeData.first
            else { await errorResponse(type(of: pdu).attributeOpcode, .attributeNotFound, pdu.startHandle); return }
        
        let mtu = Int(await connection.maximumTransmissionUnit.rawValue)
        
        let valueLength = firstAttribute.value.count
        
