static HAPError HandleWriteRequest()

in HAP/HAPBLEPeripheralManager.c [1120:1437]


static HAPError HandleWriteRequest(
        HAPPlatformBLEPeripheralManagerRef blePeripheralManager,
        HAPPlatformBLEPeripheralManagerConnectionHandle connectionHandle,
        HAPPlatformBLEPeripheralManagerAttributeHandle attributeHandle,
        void* bytes,
        size_t numBytes,
        void* _Nullable context) {
    HAPPrecondition(blePeripheralManager);
    HAPPrecondition(attributeHandle);
    HAPPrecondition(bytes);
    HAPPrecondition(numBytes);
    HAPPrecondition(context);
    HAPAccessoryServerRef* server_ = context;
    HAPAccessoryServer* server = (HAPAccessoryServer*) server_;
    HAPPrecondition(server->ble.storage->session);
    HAPSessionRef* session = server->ble.storage->session;

    HAPError err;

    HAPLogDebug(&logObject, "%s(0x%04x, 0x%04x)", __func__, connectionHandle, attributeHandle);
    HAPPrecondition(server->ble.connection.connected);
    HAPPrecondition(connectionHandle == server->ble.connection.connectionHandle);
    HAPBLEGATTTableElement* _Nullable gattAttribute = GetGATTAttribute(server_, attributeHandle);
    HAPPrecondition(gattAttribute);
    const HAPBaseCharacteristic* _Nullable characteristic = gattAttribute->characteristic;
    const HAPService* _Nullable service = gattAttribute->service;
    const HAPAccessory* _Nullable accessory = gattAttribute->accessory;

    if (attributeHandle == gattAttribute->valueHandle) {
        HAPAssert(characteristic);
        HAPAssert(service);
        HAPAssert(accessory);
        HAPLogCharacteristicDebug(&logObject, characteristic, service, accessory, "GATT Write value.");

        // Get HAP-BLE procedure.
        HAPBLEProcedureType procedureType;
        void* procedure;
        bool isNewProcedure;
        err = AttachProcedure(server_, session, gattAttribute, &procedureType, &procedure, &isNewProcedure);
        if (err) {
            HAPAssert(err == kHAPError_InvalidState || err == kHAPError_OutOfResources);
            HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
            return err;
        }

        // Process request.
        switch (procedureType) {
            case kHAPBLEProcedureType_Full: {
                HAPBLEProcedureRef* fullProcedure = procedure;

                // Process request.
                err = HAPBLEProcedureHandleGATTWrite(fullProcedure, bytes, numBytes);
                if (err) {
                    HAPAssert(
                            err == kHAPError_InvalidState || err == kHAPError_InvalidData ||
                            err == kHAPError_OutOfResources);
                    HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                    return err;
                }
            } break;
            case kHAPBLEProcedureType_Fallback: {
                HAPBLEFallbackProcedure* fallbackProcedure = procedure;

                // When Pair Verify is accessed, all fallback procedures are cancelled.
                // Therefore, we do not need to remember whether or not the procedure has been secured at start.
                if (HAPSessionIsSecured(session)) {
                    if (numBytes < CHACHA20_POLY1305_TAG_BYTES) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Write to fallback procedure malformed (too short for auth tag).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }
                    err = HAPSessionDecryptControlMessage(server_, session, bytes, bytes, numBytes);
                    if (err) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "First fragment of fallback procedure malformed (decryption failed).");
                        HAPAssert(err == kHAPError_InvalidState || err == kHAPError_InvalidData);
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return err;
                    }
                    numBytes -= CHACHA20_POLY1305_TAG_BYTES;
                }

                if (isNewProcedure) {
                    HAPLogCharacteristicInfo(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Processing first fragment of fallback procedure.");

                    uint8_t* data = bytes;
                    if (numBytes < 5) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "First fragment of fallback procedure malformed (too short).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }
                    if (data[0] != ((0 << 7) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0))) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "First fragment of fallback procedure malformed (control field).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }

                    // Store minimal information to be able to throw error.
                    fallbackProcedure->transactionID = data[2];
                    fallbackProcedure->status = kHAPBLEFallbackProcedureStatus_MaxProcedures;

                    // Handle simple errors.
                    uint8_t operation = data[1];
                    uint16_t iid = HAPReadLittleUInt16(&data[3]);
                    if (HAPPDUIsValidOpcode(operation)) {
                        uint16_t expectedIID;
                        if (HAPBLEPDUOpcodeIsServiceOperation((HAPPDUOpcode) operation)) {
                            HAPAssert(service->iid <= UINT16_MAX);
                            expectedIID = (uint16_t) service->iid;
                        } else {
                            HAPAssert(characteristic->iid <= UINT16_MAX);
                            expectedIID = (uint16_t) characteristic->iid;
                        }

                        if (iid != expectedIID) {
                            HAPLogCharacteristic(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Invalid IID %u in fallback procedure.",
                                    iid);

                            fallbackProcedure->status = kHAPBLEFallbackProcedureStatus_InvalidInstanceID;

                            // 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
                            if (operation == kHAPPDUOpcode_ServiceSignatureRead && !iid) {
                                fallbackProcedure->status =
                                        kHAPBLEFallbackProcedureStatus_ZeroInstanceIDServiceSignatureRead;
                            }
                        }
                    }

                    // Skip body.
                    if (numBytes > 5) {
                        if (numBytes < 7) {
                            HAPLogCharacteristic(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "First fragment of fallback procedure on malformed (body length).");
                            HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                            return kHAPError_InvalidData;
                        }

                        fallbackProcedure->remainingBodyBytes = HAPReadLittleUInt16(&data[5]);

                        // Skip body.
                        if (fallbackProcedure->remainingBodyBytes < numBytes - 7) {
                            HAPLogCharacteristic(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "First fragment of fallback procedure on malformed (body too long).");
                            HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                            return kHAPError_InvalidData;
                        }
                        fallbackProcedure->remainingBodyBytes -= (uint16_t)(numBytes - 7);
                    } else {
                        fallbackProcedure->remainingBodyBytes = 0;
                    }
                } else {
                    HAPLogCharacteristicInfo(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Processing continuation of fallback procedure.");

                    uint8_t* data = bytes;
                    if (numBytes < 2) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Continuation of fallback procedure malformed (too short).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }
                    if (data[0] != ((1 << 7) | (0 << 3) | (0 << 2) | (0 << 1) | (0 << 0))) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Continuation of fallback procedure malformed (control field).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }
                    if (data[1] != fallbackProcedure->transactionID) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Continuation of fallback procedure malformed (invalid TID).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }

                    // Skip body.
                    if (fallbackProcedure->remainingBodyBytes < numBytes - 2) {
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Continuation of fallback procedure malformed (body too long).");
                        HAPSessionInvalidate(server_, session, /* terminateLink: */ true);
                        return kHAPError_InvalidData;
                    }
                    fallbackProcedure->remainingBodyBytes -= (uint16_t)(numBytes - 2);
                }

                // Report response being sent.
                HAPBLESessionDidSendGATTResponse(server_, session);
            } break;
        }

        // Continue sending events (if security state changed).
        SendPendingEventNotifications(server_);
    } else if (attributeHandle == gattAttribute->cccDescriptorHandle) {
        HAPAssert(characteristic);
        HAPAssert(service);
        HAPAssert(accessory);
        HAPLogCharacteristicDebug(
                &logObject,
                characteristic,
                service,
                accessory,
                "GATT Write Client Characteristic Configuration descriptor value.");

        // Process request.
        if (numBytes != 2) {
            HAPLogCharacteristic(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "Unexpected Client Characteristic Configuration descriptor length: %lu.",
                    (unsigned long) numBytes);
            return kHAPError_InvalidData;
        }
        uint16_t v = HAPReadLittleUInt16(bytes);
        if (v & ~0x0002) {
            HAPLogCharacteristic(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "Unexpected Client Characteristic Configuration descriptor value: 0x%04x.",
                    (unsigned int) v);
            return kHAPError_InvalidData;
        }
        bool eventsEnabled = (v & 0x0002) != 0;
        SetNotificationsEnabled(server_, session, gattAttribute, eventsEnabled);
    } else {
        HAPAssert(attributeHandle == gattAttribute->iidHandle);
        HAPAssert(service);
        HAPAssert(accessory);
        if (characteristic) {
            HAPLogCharacteristicDebug(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "GATT Write Characteristic Instance ID descriptor value.");

            // Process request.
            HAPLogCharacteristic(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "Rejecting write to Characteristic Instance ID descriptor value.");
            return kHAPError_InvalidState;
        } else {
            HAPLogServiceDebug(&logObject, service, accessory, "GATT Write Service Instance ID descriptor value.");

            // Process request.
            HAPLogService(&logObject, service, accessory, "Rejecting write to Service Instance ID descriptor value.");
            return kHAPError_InvalidState;
        }
    }
    return kHAPError_None;
}