static HAPError HAPBLEProcedureProcessTransaction()

in HAP/HAPBLEProcedure.c [170:1059]


static HAPError HAPBLEProcedureProcessTransaction(HAPBLEProcedureRef* bleProcedure_) {
    HAPPrecondition(bleProcedure_);
    HAPBLEProcedure* bleProcedure = (HAPBLEProcedure*) bleProcedure_;
    HAPPrecondition(bleProcedure->server);
    HAPAccessoryServer* server = (HAPAccessoryServer*) bleProcedure->server;
    HAPPrecondition(bleProcedure->accessory);
    const HAPAccessory* accessory = bleProcedure->accessory;
    HAPPrecondition(bleProcedure->service);
    const HAPService* service = bleProcedure->service;
    HAPPrecondition(bleProcedure->characteristic);
    const HAPBaseCharacteristic* characteristic = bleProcedure->characteristic;

    HAPError err;

    // Get request.
    HAPBLETransactionRequest request;
    err = HAPBLETransactionGetRequest(&bleProcedure->transaction, &request);
    if (err) {
        HAPAssert(err == kHAPError_OutOfResources);
        SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
    }

    // Validate opcode.
    if (!HAPPDUIsValidOpcode(request.opcode)) {
        // If an accessory receives a HAP PDU with an opcode that it does not support it shall reject the PDU and
        // respond with a status code Unsupported PDU in its HAP response.
        // See HomeKit Accessory Protocol Specification R14
        // Section 7.3.3.2 HAP Request Format
        HAPLogCharacteristic(
                &logObject,
                characteristic,
                service,
                accessory,
                "Rejected request with unsupported opcode: 0x%02x.",
                request.opcode);
        SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
    }

    // Check that HAP request's characteristic / service instance ID matches the addressed characteristic's instance ID.
    HAPAssert(service->iid <= UINT16_MAX);
    HAPAssert(characteristic->iid <= UINT16_MAX);
    uint16_t iid;
    if (HAPBLEPDUOpcodeIsServiceOperation(request.opcode)) {
        iid = (uint16_t) service->iid;
    } else {
        iid = (uint16_t) characteristic->iid;
    }
    if (request.iid != iid) {
        if (HAPBLEPDUOpcodeIsServiceOperation(request.opcode)) {
            HAPLogService(
                    &logObject,
                    service,
                    accessory,
                    "Request's IID [00000000%08X] does not match the addressed IID.",
                    request.iid);
        } else {
            HAPLogCharacteristic(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "Request's IID [00000000%08X] does not match the addressed IID.",
                    request.iid);
        }

        if (request.opcode == kHAPPDUOpcode_ServiceSignatureRead) {
            // If the accessory receives an invalid (eg., 0) service instance ID in the
            // HAP-Service-Signature-Read-Request, it must respond with a valid HAP-Service-Signature-Read-Response with
            // Svc Properties set to 0 and Linked Svc (if applicable) set to 0 length.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.4.13 HAP-Service-Signature-Read-Response
        } else {
            SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidInstanceID);
        }
    }

    // Handle request.
    bool hasReturnResponse = true;
    switch (request.opcode) {
        case kHAPPDUOpcode_ServiceSignatureRead: {
            // Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Service-Signature-Read-Request");
                return kHAPError_InvalidState;
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.4.4.5.4 Service Signature Characteristic

            if (!HAPBLECharacteristicSupportsServiceProcedures(characteristic)) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Not supported.",
                        "HAP-Service-Signature-Read-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // HAP-Service-Signature-Read-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Service-Signature-Read-Response.
            err = HAPBLEServiceGetSignatureReadResponse(request.iid == iid ? service : NULL, &writer);
            if (err) {
                HAPAssert(err == kHAPError_OutOfResources);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }

            SEND_RESPONSE_AND_RETURN(&writer);
        }
        case kHAPPDUOpcode_CharacteristicSignatureRead: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Characteristic-Signature-Read-Request");
                return kHAPError_InvalidState;
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.1 HAP Characteristic Signature Read Procedure

            // The characteristics `Pair Setup`, `Pair Verify` and `Pairing Features` of `Pairing Service`
            // do not support "Paired Read" and "Paired Write" and only support the
            // `HAP Characteristic Signature Read Procedure` without a secure session.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.1 HAP Characteristic Signature Read Procedure
            if (HAPSessionIsSecured(bleProcedure->session) &&
                HAPBLECharacteristicDropsSecuritySession(characteristic)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only non-secure access is permitted.",
                        "HAP-Characteristic-Signature-Read-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // HAP-Characteristic-Signature-Read-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Characteristic-Signature-Read-Response.
            err = HAPBLECharacteristicGetSignatureReadResponse(characteristic, service, &writer);
            if (err) {
                HAPAssert(err == kHAPError_OutOfResources);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            SEND_RESPONSE_AND_RETURN(&writer);
        }
        case kHAPPDUOpcode_CharacteristicConfiguration: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Characteristic-Configuration-Request");
                return kHAPError_InvalidState;
            }
            if (HAPSessionIsTransient(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Session is transient.",
                        "HAP-Characteristic-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.8 HAP Characteristic Configuration Procedure

            if (!HAPSessionIsSecured(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only secure access is permitted.",
                        "HAP-Characteristic-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // Handle HAP-Characteristic-Configuration-Request.
            err = HAPBLECharacteristicHandleConfigurationRequest(
                    characteristic, service, accessory, &request.bodyReader, server->platform.keyValueStore);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_InvalidData);
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Request handling failed with error %d.",
                        "HAP-Characteristic-Configuration-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }

            // HAP-Characteristic-Configuration-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Characteristic-Configuration-Response.
            err = HAPBLECharacteristicGetConfigurationResponse(
                    characteristic, service, accessory, &writer, server->platform.keyValueStore);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_OutOfResources);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            SEND_RESPONSE_AND_RETURN(&writer);
        }
        case kHAPPDUOpcode_ProtocolConfiguration: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Protocol-Configuration-Request");
                return kHAPError_InvalidState;
            }
            if (HAPSessionIsTransient(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Session is transient.",
                        "HAP-Protocol-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.9 HAP Protocol Configuration Procedure

            if (!HAPBLECharacteristicSupportsServiceProcedures(characteristic)) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Not supported.",
                        "HAP-Protocol-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            if (!service->properties.ble.supportsConfiguration) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Service does not support configuration.",
                        "HAP-Protocol-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            if (!HAPSessionIsSecured(bleProcedure->session)) {
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Only secure access is permitted.",
                        "HAP-Protocol-Configuration-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // Handle HAP-Protocol-Configuration-Request.
            bool didRequestGetAll;
            err = HAPBLEProtocolHandleConfigurationRequest(
                    bleProcedure->server,
                    bleProcedure->session,
                    service,
                    accessory,
                    &request.bodyReader,
                    &didRequestGetAll,
                    server->platform.keyValueStore);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_InvalidData);
                HAPLogService(
                        &logObject,
                        service,
                        accessory,
                        "Rejected %s: Request handling failed with error %d.",
                        "HAP-Protocol-Configuration-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            if (!didRequestGetAll) {
                SEND_RESPONSE_AND_RETURN(NULL);
            }

            // HAP-Protocol-Configuration-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Protocol-Configuration-Response.
            err = HAPBLEProtocolGetConfigurationResponse(
                    bleProcedure->server,
                    bleProcedure->session,
                    service,
                    accessory,
                    &writer,
                    server->platform.keyValueStore);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_OutOfResources);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            SEND_RESPONSE_AND_RETURN(&writer);
        }
        case kHAPPDUOpcode_Token: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Token-Request");
                return kHAPError_InvalidState;
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 5.15.1 HAP-Token-Request

            if (!HAPUUIDAreEqual(service->serviceType, &kHAPServiceType_HAPProtocolInformation) ||
                !HAPUUIDAreEqual(characteristic->characteristicType, &kHAPCharacteristicType_ServiceSignature)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only supported on the Service Signature characteristic in the "
                        "HAP Protocol Information Service.",
                        "HAP-Token-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            if (!HAPSessionIsSecured(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only secure access is permitted.",
                        "HAP-Token-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // HAP-Token-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Token-Response.
            err = HAPMFiTokenAuthGetTokenResponse(bleProcedure->server, bleProcedure->session, accessory, &writer);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_InvalidState || err == kHAPError_OutOfResources);
                HAPLogAccessory(
                        &logObject,
                        accessory,
                        "Rejected %s: Request handling failed with error %u.",
                        "HAP-Token-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            SEND_RESPONSE_AND_RETURN(&writer);
        }
        case kHAPPDUOpcode_TokenUpdate: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Token-Update-Request");
                return kHAPError_InvalidState;
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 5.15.3 HAP-Token-Update-Request

            if (!HAPUUIDAreEqual(service->serviceType, &kHAPServiceType_HAPProtocolInformation) ||
                !HAPUUIDAreEqual(characteristic->characteristicType, &kHAPCharacteristicType_ServiceSignature)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only supported on the Service Signature characteristic in the "
                        "HAP Protocol Information Service.",
                        "HAP-Token-Update-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            if (!HAPSessionIsSecured(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only secure access is permitted.",
                        "HAP-Token-Update-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // Handle HAP-Token-Update-Request.
            err = HAPMFiTokenAuthHandleTokenUpdateRequest(
                    bleProcedure->server, bleProcedure->session, accessory, &request.bodyReader);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_InvalidData);
                HAPLogAccessory(
                        &logObject,
                        accessory,
                        "Rejected %s: Request handling failed with error %u.",
                        "HAP-Token-Update-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }

            // Send HAP-Token-Update-Response.
            SEND_RESPONSE_AND_RETURN(NULL);
        }
        case kHAPPDUOpcode_Info: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Info-Request");
                return kHAPError_InvalidState;
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 5.15.5 HAP-Info-Request

            if (!HAPUUIDAreEqual(service->serviceType, &kHAPServiceType_HAPProtocolInformation) ||
                !HAPUUIDAreEqual(characteristic->characteristicType, &kHAPCharacteristicType_ServiceSignature)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only supported on Service Signature characteristic in the "
                        "HAP Protocol Information Service.",
                        "HAP-Info-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            if (!HAPSessionIsSecured(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only secure access is permitted.",
                        "HAP-Info-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // HAP-Info-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Info-Response.
            err = HAPAccessoryGetInfoResponse(bleProcedure->server, bleProcedure->session, accessory, &writer);
            if (err) {
                HAPAssert(err == kHAPError_Unknown || err == kHAPError_OutOfResources);
                HAPLogAccessory(
                        &logObject,
                        accessory,
                        "Rejected %s: Request handler failed with error %d.",
                        "HAP-Info-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            SEND_RESPONSE_AND_RETURN(&writer);
        };
        case kHAPPDUOpcode_CharacteristicTimedWrite: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Characteristic-Timed-Write-Request");
                return kHAPError_InvalidState;
            }
            if (HAPSessionIsTransient(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Session is transient.",
                        "HAP-Characteristic-Timed-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.4 HAP Characteristic Timed Write Procedure

            // Cache body.
            bleProcedure->multiTransactionType = kHAPBLEProcedureMultiTransactionType_TimedWrite;
            HAPAssert(sizeof bleProcedure->_.timedWrite.bodyReader == sizeof request.bodyReader);
            HAPRawBufferCopyBytes(
                    &bleProcedure->_.timedWrite.bodyReader,
                    &request.bodyReader,
                    sizeof bleProcedure->_.timedWrite.bodyReader);

            // The accessory must start the TTL timer after sending the HAP-Characteristic-Timed-Write-Response.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.4 HAP Characteristic Timed Write Procedure
            bleProcedure->_.timedWrite.timedWriteStartTime = HAPPlatformClockGetCurrent();
            SEND_RESPONSE_AND_RETURN(NULL);
        }
        case kHAPPDUOpcode_CharacteristicExecuteWrite: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_TimedWrite) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: No timed write in progress.",
                        "HAP-Characteristic-Execute-Write-Request");
                return kHAPError_InvalidState;
            }
            HAPAssert(!HAPSessionIsTransient(bleProcedure->session));

            bleProcedure->multiTransactionType = kHAPBLEProcedureMultiTransactionType_None;

            HAPAssert(sizeof request.bodyReader == sizeof bleProcedure->_.timedWrite.bodyReader);
            HAPRawBufferCopyBytes(
                    &request.bodyReader, &bleProcedure->_.timedWrite.bodyReader, sizeof request.bodyReader);

            // Although undocumented, the pending Timed Write request may also include the Return Response flag and AAD.

            // Request body has been restored and is still available.
        } // Fallthrough.
        case kHAPPDUOpcode_CharacteristicWrite: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Characteristic-Write-Request");
                return kHAPError_InvalidState;
            }
            if (HAPSessionIsTransient(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Session is transient.",
                        "HAP-Characteristic-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.2 HAP Characteristic Write Procedure

            // Fetch permissions.
            bool supportsWrite = characteristic->properties.ble.writableWithoutSecurity;
            bool supportsSecureWrite = characteristic->properties.writable;

            // Unpaired Identify must be allowed only if the accessory is unpaired, i.e. it has no paired controllers.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.4.1.9 Unpaired Identify
            if (HAPUUIDAreEqual(characteristic->characteristicType, &kHAPCharacteristicType_Identify)) {
                supportsWrite = !HAPAccessoryServerIsPaired(bleProcedure->server);
            }

            // Check permissions.
            bool sessionIsSecured = HAPSessionIsSecured(bleProcedure->session);
            if (!sessionIsSecured && !supportsWrite) {
                if (supportsSecureWrite) {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Only secure writes are supported.",
                            "HAP-Characteristic-Write-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InsufficientAuthentication);
                } else {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Not supported.",
                            "HAP-Characteristic-Write-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                }
            }
            if (sessionIsSecured && !supportsSecureWrite) {
                if (supportsWrite) {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Only non-secure writes are supported.",
                            "HAP-Characteristic-Write-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                } else {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Not supported.",
                            "HAP-Characteristic-Write-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                }
            }
            if (HAPCharacteristicWriteRequiresAdminPermissions(characteristic) &&
                !HAPSessionControllerIsAdmin(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Requires controller to have admin permissions.",
                        "HAP-Characteristic-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InsufficientAuthentication);
            }

            // Check for Timed Write requirement.
            bool isTimedWrite = request.opcode == kHAPPDUOpcode_CharacteristicExecuteWrite;
            bool requiresTimedWrite = characteristic->properties.requiresTimedWrite;
            if (!isTimedWrite && requiresTimedWrite) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Only timed writes are supported.",
                        "HAP-Characteristic-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }

            // Destroy request body and process HAP-Characteristic-Write-Request.
            HAPAssert(server->ble.connection.connected);
            HAPAssert(!server->ble.connection.write.characteristic);
            HAPAssert(!server->ble.connection.write.service);
            HAPAssert(!server->ble.connection.write.accessory);
            server->ble.connection.write.characteristic = characteristic;
            server->ble.connection.write.service = service;
            server->ble.connection.write.accessory = accessory;
            bool hasExpired;
            err = HAPBLECharacteristicParseAndWriteValue(
                    bleProcedure->server,
                    bleProcedure->session,
                    characteristic,
                    service,
                    accessory,
                    &request.bodyReader,
                    isTimedWrite ? &bleProcedure->_.timedWrite.timedWriteStartTime : NULL,
                    &hasExpired,
                    &hasReturnResponse);
            server->ble.connection.write.characteristic = NULL;
            server->ble.connection.write.service = NULL;
            server->ble.connection.write.accessory = NULL;
            if (err == kHAPError_NotAuthorized) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Write failed due to insufficient authorization.",
                        "HAP-Characteristic-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InsufficientAuthorization);
            } else if (err) {
                HAPAssert(
                        err == kHAPError_Unknown || err == kHAPError_InvalidState || err == kHAPError_InvalidData ||
                        err == kHAPError_OutOfResources || err == kHAPError_Busy);
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Write failed with error %d.",
                        "HAP-Characteristic-Write-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            if (hasExpired) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Timed Write expired.",
                        "HAP-Characteristic-Write-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }
            if (!hasReturnResponse) {
                if (characteristic->properties.ip.supportsWriteResponse) {
                    // The supportsWriteResponse characteristic property provides a guarantee to the application
                    // that the characteristic's handleRead callback is always called after a successful handleWrite.
                    // Whether the controller actually requested write response is hidden from the application.
                    // Although write response is mainly used in the HAP over IP transport it makes sense
                    // to follow the same behaviour when such a characteristic is accessed using HAP over Bluetooth LE.
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Characteristic supports write response: Calling read handler.");
                } else {
                    SEND_RESPONSE_AND_RETURN(NULL);
                }
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.5 HAP Characteristic Write-with-Response Procedure.

            // Request body has been destroyed!
        } // Fallthrough.
        case kHAPPDUOpcode_CharacteristicRead: {
            // 10. Accessory must support only one HAP procedure on a characteristic at any point in time.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.5 Testing Bluetooth LE Accessories
            if (bleProcedure->multiTransactionType != kHAPBLEProcedureMultiTransactionType_None) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Different HAP procedure in progress.",
                        "HAP-Characteristic-Read-Request");
                return kHAPError_InvalidState;
            }
            if (HAPSessionIsTransient(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Session is transient.",
                        "HAP-Characteristic-Read-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
            }

            // See HomeKit Accessory Protocol Specification R14
            // Section 7.3.5.3 HAP Characteristic Read Procedure

            // Check permissions.
            bool sessionIsSecured = HAPSessionIsSecured(bleProcedure->session);
            bool supportsRead = characteristic->properties.ble.readableWithoutSecurity;
            bool supportsSecureRead = characteristic->properties.readable;
            if (!sessionIsSecured && !supportsRead) {
                if (supportsSecureRead) {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Only secure reads are supported.",
                            "HAP-Characteristic-Read-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InsufficientAuthentication);
                } else {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Not supported.",
                            "HAP-Characteristic-Read-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                }
            }
            if (sessionIsSecured && !supportsSecureRead) {
                if (supportsRead) {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Only non-secure reads are supported.",
                            "HAP-Characteristic-Read-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                } else {
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Rejected %s: Not supported.",
                            "HAP-Characteristic-Read-Request");
                    SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_UnsupportedPDU);
                }
            }
            if (HAPCharacteristicReadRequiresAdminPermissions(characteristic) &&
                !HAPSessionControllerIsAdmin(bleProcedure->session)) {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Requires controller to have admin permissions.",
                        "HAP-Characteristic-Read-Request");
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InsufficientAuthentication);
            }

            // HAP-Characteristic-Read-Request ok.
            HAPTLVWriterRef writer;
            DestroyRequestBodyAndCreateResponseBodyWriter(bleProcedure_, &writer);

            // Serialize HAP-Characteristic-Read-Response.
            err = HAPBLECharacteristicReadAndSerializeValue(
                    bleProcedure->server, bleProcedure->session, characteristic, service, accessory, &writer);
            if (err) {
                HAPAssert(
                        err == kHAPError_Unknown || err == kHAPError_InvalidState || err == kHAPError_OutOfResources ||
                        err == kHAPError_Busy);
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Rejected %s: Read failed with error %d.",
                        "HAP-Characteristic-Read-Request",
                        err);
                SEND_ERROR_AND_RETURN(kHAPBLEPDUStatus_InvalidRequest);
            }
            if (hasReturnResponse) {
                SEND_RESPONSE_AND_RETURN(&writer);
            } else {
                HAPLogCharacteristic(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "HAP-Param-Return-Response not set: Discarding write response.");
                SEND_RESPONSE_AND_RETURN(NULL);
            }
        }
    }
    HAPFatalError();
}