HAPError HAPBLEAccessoryServerDidRaiseEvent()

in HAP/HAPBLEAccessoryServer+Advertising.c [698:1090]


HAPError HAPBLEAccessoryServerDidRaiseEvent(
        HAPAccessoryServerRef* server_,
        const HAPCharacteristic* characteristic_,
        const HAPService* service,
        const HAPAccessory* accessory,
        HAPSessionRef* _Nullable session) {
    HAPPrecondition(server_);
    HAPAccessoryServer* server = (HAPAccessoryServer*) server_;
    HAPPrecondition(characteristic_);
    const HAPBaseCharacteristic* characteristic = characteristic_;
    HAPPrecondition(service);
    HAPPrecondition(accessory);
    HAPPrecondition(accessory->aid == 1);

    HAPError err;

    if (characteristic->properties.supportsEventNotification) {
        // Connected event.
        if (server->ble.connection.connected && (!session || session == server->ble.storage->session)) {
            const HAPCharacteristic* writtenCharacteristic = server->ble.connection.write.characteristic;
            const HAPService* writtenService = server->ble.connection.write.service;
            const HAPAccessory* writtenAccessory = server->ble.connection.write.accessory;
            if (characteristic_ == writtenCharacteristic && service == writtenService &&
                accessory == writtenAccessory) {
                HAPLogCharacteristicInfo(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Suppressing notification as the characteristic is currently being written.");
            } else {
                HAPBLEPeripheralManagerRaiseEvent(server_, characteristic_, service, accessory);
            }
        }
    }

    if (characteristic->properties.ble.supportsBroadcastNotification) {
        // Broadcasted event.
        // See HomeKit Accessory Protocol Specification R14
        // Section 7.4.6.2 Broadcasted Events

        // If a controller connects to the accessory before the completion of the 3 second advertising period the
        // accessory should abort the encrypted advertisement and continue with its regular advertisement at the regular
        // advertising period after the controller disconnects.
        // See HomeKit Accessory Protocol Specification R14
        // Section 7.4.6.2 Broadcasted Events
        if (!server->ble.adv.connected) {
            uint16_t keyExpirationGSN;
            err = HAPBLEAccessoryServerBroadcastGetParameters(
                    server->platform.keyValueStore, &keyExpirationGSN, NULL, NULL);
            if (err) {
                HAPAssert(err == kHAPError_Unknown);
                return err;
            }
            HAPBLEAccessoryServerGSN gsn;
            err = HAPBLEAccessoryServerGetGSN(server->platform.keyValueStore, &gsn);
            if (err) {
                HAPAssert(err == kHAPError_Unknown);
                return err;
            }

            // Characteristic changes while in a broadcast encryption key expired state shall not use broadcasted events
            // and must fall back to disconnected/connected events until the controller has re-generated a new broadcast
            // encryption key and re-registered characteristics for broadcasted notification.
            // See HomeKit Accessory Protocol Specification R14
            // Section 7.4.7.4 Broadcast Encryption Key expiration and refresh
            if (keyExpirationGSN && keyExpirationGSN != gsn.gsn) {
                HAPBLECharacteristicBroadcastInterval interval;
                bool enabled;
                err = HAPBLECharacteristicGetBroadcastConfiguration(
                        characteristic, service, accessory, &enabled, &interval, server->platform.keyValueStore);
                if (err) {
                    HAPAssert(err == kHAPError_Unknown);
                    return err;
                }

                if (enabled) {
                    // For additional characteristic changes before the completion of the 3 second period and before
                    // a controller connection, the GSN should be updated again and the accessory must reflect the
                    // latest changed characteristic value in its encrypted advertisement and continue to broadcast
                    // for an additional 3 seconds from the last change.
                    // See HomeKit Accessory Protocol Specification R14
                    // Section 7.4.6.2 Broadcasted Events
                    if (server->ble.adv.timer) {
                        HAPPlatformTimerDeregister(server->ble.adv.timer);
                        server->ble.adv.timer = 0;
                    }

                    // Cancel current broadcast.
                    server->ble.adv.broadcastedEvent.iid = 0;
                    HAPRawBufferZero(
                            server->ble.adv.broadcastedEvent.value, sizeof server->ble.adv.broadcastedEvent.value);
                    HAPAccessoryServerUpdateAdvertisingData(server_);

                    // Fetch characteristic value.
                    // When the characteristic value is less than 8 bytes the remaining bytes shall be set to 0.
                    // See HomeKit Accessory Protocol Specification R14
                    // Section 7.4.2.2.2 Manufacturer Data
                    err = kHAPError_Unknown;
                    uint8_t* bytes = server->ble.adv.broadcastedEvent.value;
                    HAPAssert(sizeof server->ble.adv.broadcastedEvent.value == 8);
                    switch (characteristic->format) {
                        case kHAPCharacteristicFormat_Data: {
                            // Characteristics with format of string or data/tlv8 cannot be used
                            // in broadcast notifications
                            // See HomeKit Accessory Protocol Specification R14
                            // Section 7.4.2.2.2 Manufacturer Data
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "%s characteristic cannot be used in broadcast notifications.",
                                    "Data");
                        } break;
                        case kHAPCharacteristicFormat_Bool: {
                            bool value;
                            err = HAPBoolCharacteristicHandleRead(
                                    server_,
                                    &(const HAPBoolCharacteristicReadRequest) { .transportType = kHAPTransportType_BLE,
                                                                                .session = NULL,
                                                                                .characteristic = characteristic_,
                                                                                .service = service,
                                                                                .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            bytes[0] = (uint8_t)(value ? 1 : 0);
                        } break;
                        case kHAPCharacteristicFormat_UInt8: {
                            uint8_t value;
                            err = HAPUInt8CharacteristicHandleRead(
                                    server_,
                                    &(const HAPUInt8CharacteristicReadRequest) { .transportType = kHAPTransportType_BLE,
                                                                                 .session = NULL,
                                                                                 .characteristic = characteristic_,
                                                                                 .service = service,
                                                                                 .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            bytes[0] = value;
                        } break;
                        case kHAPCharacteristicFormat_UInt16: {
                            uint16_t value;
                            err = HAPUInt16CharacteristicHandleRead(
                                    server_,
                                    &(const HAPUInt16CharacteristicReadRequest) { .transportType =
                                                                                          kHAPTransportType_BLE,
                                                                                  .session = NULL,
                                                                                  .characteristic = characteristic_,
                                                                                  .service = service,
                                                                                  .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            HAPWriteLittleUInt16(bytes, value);
                        } break;
                        case kHAPCharacteristicFormat_UInt32: {
                            uint32_t value;
                            err = HAPUInt32CharacteristicHandleRead(
                                    server_,
                                    &(const HAPUInt32CharacteristicReadRequest) { .transportType =
                                                                                          kHAPTransportType_BLE,
                                                                                  .session = NULL,
                                                                                  .characteristic = characteristic_,
                                                                                  .service = service,
                                                                                  .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            HAPWriteLittleUInt32(bytes, value);
                        } break;
                        case kHAPCharacteristicFormat_UInt64: {
                            uint64_t value;
                            err = HAPUInt64CharacteristicHandleRead(
                                    server_,
                                    &(const HAPUInt64CharacteristicReadRequest) { .transportType =
                                                                                          kHAPTransportType_BLE,
                                                                                  .session = NULL,
                                                                                  .characteristic = characteristic_,
                                                                                  .service = service,
                                                                                  .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            HAPWriteLittleUInt64(bytes, value);
                        } break;
                        case kHAPCharacteristicFormat_Int: {
                            int32_t value;
                            err = HAPIntCharacteristicHandleRead(
                                    server_,
                                    &(const HAPIntCharacteristicReadRequest) { .transportType = kHAPTransportType_BLE,
                                                                               .session = NULL,
                                                                               .characteristic = characteristic_,
                                                                               .service = service,
                                                                               .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            HAPWriteLittleInt32(bytes, value);
                        } break;
                        case kHAPCharacteristicFormat_Float: {
                            float value;
                            err = HAPFloatCharacteristicHandleRead(
                                    server_,
                                    &(const HAPFloatCharacteristicReadRequest) { .transportType = kHAPTransportType_BLE,
                                                                                 .session = NULL,
                                                                                 .characteristic = characteristic_,
                                                                                 .service = service,
                                                                                 .accessory = accessory },
                                    &value,
                                    server->context);
                            if (err) {
                                break;
                            }
                            uint32_t bitPattern = HAPFloatGetBitPattern(value);
                            HAPWriteLittleUInt32(bytes, bitPattern);
                        } break;
                        case kHAPCharacteristicFormat_String: {
                            // Characteristics with format of string or data/tlv8 cannot be used
                            // in broadcast notifications.
                            // See HomeKit Accessory Protocol Specification R14
                            // Section 7.4.2.2.2 Manufacturer Data
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "%s characteristic cannot be used in broadcast notifications.",
                                    "String");
                        }
                            HAPFatalError();
                        case kHAPCharacteristicFormat_TLV8: {
                            // Characteristics with format of string or data/tlv8 cannot be used
                            // in broadcast notifications".
                            // See HomeKit Accessory Protocol Specification R14
                            // Section 7.4.2.2.2 Manufacturer Data
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "%s characteristic cannot be used in broadcast notifications.",
                                    "TLV8");
                        }
                            HAPFatalError();
                    }
                    if (err) {
                        HAPAssert(
                                err == kHAPError_Unknown || err == kHAPError_InvalidState ||
                                err == kHAPError_OutOfResources || err == kHAPError_Busy);
                        HAPLogCharacteristic(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Value for broadcast notification could not be received. Skipping event!");
                    } else {
                        // Increment GSN.
                        err = HAPBLEAccessoryServerIncrementGSN(server_);
                        if (err) {
                            HAPAssert(err == kHAPError_Unknown);
                            return err;
                        }

                        err = HAPPlatformTimerRegister(
                                &server->ble.adv.timer,
                                HAPPlatformClockGetCurrent() + server->ble.adv.ev_duration,
                                AdvertisingTimerExpired,
                                server_);
                        if (err) {
                            HAPAssert(err == kHAPError_OutOfResources);
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Not enough resources to start broadcast event timer. Skipping event!");
                        } else {
                            // Initialize broadcasted event.
                            server->ble.adv.broadcastedEvent.interval = interval;
                            HAPAssert(characteristic->iid <= UINT16_MAX);
                            server->ble.adv.broadcastedEvent.iid = (uint16_t) characteristic->iid;
                            HAPLogCharacteristicInfo(
                                    &logObject, characteristic, service, accessory, "Broadcasted Event.");
                        }
                    }

                    // Update advertisement parameters.
                    HAPAccessoryServerUpdateAdvertisingData(server_);
                    return kHAPError_None;
                } else {
                    HAPLogCharacteristicInfo(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Broadcasted Event - Skipping: Broadcasts disabled.");
                }
            } else {
                HAPLogCharacteristicInfo(
                        &logObject,
                        characteristic,
                        service,
                        accessory,
                        "Broadcasted Event - Skipping: Broadcast Key expired.");
            }
        } else {
            HAPLogCharacteristicInfo(
                    &logObject, characteristic, service, accessory, "Broadcasted Event - Skipping: Connected.");
        }
    }

    if (characteristic->properties.ble.supportsDisconnectedNotification) {
        // Disconnected event.
        // See HomeKit Accessory Protocol Specification R14
        // Section 7.4.6.3 Disconnected Events

        HAPBLEAccessoryServerGSN gsn;
        err = HAPBLEAccessoryServerGetGSN(server->platform.keyValueStore, &gsn);
        if (err) {
            HAPAssert(err == kHAPError_Unknown);
            return err;
        }

        // The GSN should increment only once for multiple characteristic value changes while in in disconnected state
        // until the accessory state changes from disconnected to connected.
        // See HomeKit Accessory Protocol Specification R14
        // Section 7.4.6.3 Disconnected Events
        if (!gsn.didIncrement) {
            HAPAssert(!server->ble.adv.broadcastedEvent.iid);
            HAPAssert(!server->ble.adv.timer);

            err = HAPBLEAccessoryServerIncrementGSN(server_);
            if (err) {
                HAPAssert(err == kHAPError_Unknown);
                return err;
            }

            if (!server->ble.adv.connected) {
                HAPLogCharacteristicInfo(&logObject, characteristic, service, accessory, "Disconnected Event.");

                // After updating the GSN as specified in Section HAP BLE Regular Advertisement Format in the
                // disconnected state the accessory must use a 20 ms advertising interval for at least 3 seconds.
                // See HomeKit Accessory Protocol Specification R14
                // Section 7.4.6.3 Disconnected Events
                err = HAPPlatformTimerRegister(
                        &server->ble.adv.timer,
                        HAPPlatformClockGetCurrent() + server->ble.adv.ev_duration,
                        AdvertisingTimerExpired,
                        server_);
                if (err) {
                    HAPAssert(err == kHAPError_OutOfResources);
                    HAPLogCharacteristic(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Not enough resources to start disconnected event timer!");
                }

                // Update advertisement parameters.
                HAPAccessoryServerUpdateAdvertisingData(server_);
                return kHAPError_None;
            } else {
                HAPLogCharacteristicInfo(
                        &logObject, characteristic, service, accessory, "Disconnected Event - Connected (no adv).");
            }
        } else {
            HAPLogCharacteristicInfo(
                    &logObject,
                    characteristic,
                    service,
                    accessory,
                    "Disconnected Event - Skipping: GSN already incremented.");
        }
    }

    return kHAPError_None;
}