static bool AccessoryIsValid()

in HAP/HAPAccessoryValidation.c [67:749]


static bool AccessoryIsValid(const HAPAccessory* accessory) {
    HAPPrecondition(accessory);

    // Validate accessory information.
    if (!accessory->name) {
        HAPLogError(&logObject, "Accessory 0x%016llX %s is not set.", (unsigned long long) accessory->aid, "name");
        return false;
    }
    size_t numNameBytes = HAPStringGetNumBytes(accessory->name);
    if (numNameBytes > kHAPAccessory_MaxNameBytes) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s has invalid length (%zu) - expected: max %zu.",
                "name",
                accessory->name,
                numNameBytes,
                kHAPAccessory_MaxNameBytes);
        return false;
    }
    if (!HAPUTF8IsValidData(accessory->name, numNameBytes)) {
        HAPLogAccessoryError(
                &logObject, accessory, "Accessory %s %s is not a valid UTF-8 string.", "name", accessory->name);
        return false;
    }

    if (!accessory->manufacturer) {
        HAPLogError(
                &logObject, "Accessory 0x%016llX %s is not set.", (unsigned long long) accessory->aid, "manufacturer");
        return false;
    }
    size_t numManufacturerBytes = HAPStringGetNumBytes(accessory->manufacturer);
    if (numManufacturerBytes > kHAPAccessory_MaxManufacturerBytes) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s has invalid length (%zu) - expected: max %zu.",
                "manufacturer",
                accessory->manufacturer,
                numManufacturerBytes,
                kHAPAccessory_MaxManufacturerBytes);
        return false;
    }
    if (!HAPUTF8IsValidData(accessory->manufacturer, numManufacturerBytes)) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s is not a valid UTF-8 string.",
                "manufacturer",
                accessory->manufacturer);
        return false;
    }

    if (!accessory->model) {
        HAPLogError(&logObject, "Accessory 0x%016llX %s is not set.", (unsigned long long) accessory->aid, "model");
        return false;
    }
    size_t numModelBytes = HAPStringGetNumBytes(accessory->model);
    if (numModelBytes < kHAPAccessory_MinModelBytes || numModelBytes > kHAPAccessory_MaxModelBytes) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s has invalid length (%zu) - expected: min %zu, max %zu.",
                "model",
                accessory->model,
                numModelBytes,
                kHAPAccessory_MinModelBytes,
                kHAPAccessory_MaxModelBytes);
        return false;
    }
    if (!HAPUTF8IsValidData(accessory->model, numModelBytes)) {
        HAPLogAccessoryError(
                &logObject, accessory, "Accessory %s %s is not a valid UTF-8 string.", "model", accessory->model);
        return false;
    }

    if (!accessory->serialNumber) {
        HAPLogError(
                &logObject, "Accessory 0x%016llX %s is not set.", (unsigned long long) accessory->aid, "serialNumber");
        return false;
    }
    size_t numSerialNumberBytes = HAPStringGetNumBytes(accessory->serialNumber);
    if (numSerialNumberBytes < kHAPAccessory_MinSerialNumberBytes ||
        numSerialNumberBytes > kHAPAccessory_MaxSerialNumberBytes) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s has invalid length (%zu) - expected: min %zu, max %zu.",
                "serial number",
                accessory->serialNumber,
                numSerialNumberBytes,
                kHAPAccessory_MinSerialNumberBytes,
                kHAPAccessory_MaxSerialNumberBytes);
        return false;
    }
    if (!HAPUTF8IsValidData(accessory->serialNumber, numSerialNumberBytes)) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s is not a valid UTF-8 string.",
                "serialNumber",
                accessory->serialNumber);
        return false;
    }

    if (!accessory->firmwareVersion) {
        HAPLogError(
                &logObject,
                "Accessory 0x%016llX %s is not set.",
                (unsigned long long) accessory->aid,
                "firmwareVersion");
        return false;
    }
    size_t numFirmwareVersionBytes = HAPStringGetNumBytes(accessory->firmwareVersion);
    if (!HAPUTF8IsValidData(accessory->firmwareVersion, numFirmwareVersionBytes)) {
        HAPLogAccessoryError(
                &logObject,
                accessory,
                "Accessory %s %s is not a valid UTF-8 string.",
                "firmwareVersion",
                accessory->firmwareVersion);
        return false;
    }
    if (accessory->hardwareVersion) {
        size_t numHardwareVersionBytes = HAPStringGetNumBytes(HAPNonnull(accessory->hardwareVersion));
        if (!HAPUTF8IsValidData(HAPNonnull(accessory->hardwareVersion), numHardwareVersionBytes)) {
            HAPLogAccessoryError(
                    &logObject,
                    accessory,
                    "Accessory %s %s is not a valid UTF-8 string.",
                    "hardwareVersion",
                    HAPNonnull(accessory->hardwareVersion));
            return false;
        }
    }

    if (!accessory->services) {
        HAPLogAccessoryError(
                &logObject, accessory, "Accessory must at least contain the Accessory Information Service.");
        return false;
    }
    for (size_t i = 0; accessory->services[i]; i++) {
        const HAPService* service = accessory->services[i];

        if (!service->debugDescription) {
            HAPLogAccessoryError(
                    &logObject,
                    accessory,
                    "Service 0x%016llX debugDescription is not set.",
                    (unsigned long long) service->iid);
            return false;
        }
        size_t numServiceDebugDescriptionBytes = HAPStringGetNumBytes(service->debugDescription);
        if (!HAPUTF8IsValidData(service->debugDescription, numServiceDebugDescriptionBytes)) {
            HAPLogServiceError(
                    &logObject,
                    service,
                    accessory,
                    "Service debugDescription %s is not a valid UTF-8 string.",
                    service->debugDescription);
            return false;
        }

        if (service->name) {
            size_t numServiceNameBytes = HAPStringGetNumBytes(HAPNonnull(service->name));
            if (!HAPUTF8IsValidData(HAPNonnull(service->name), numServiceNameBytes)) {
                HAPLogServiceError(
                        &logObject, service, accessory, "Service name %s is not a valid UTF-8 string.", service->name);
                return false;
            }
        }

        if (service->linkedServices) {
            for (size_t j = 0; service->linkedServices[j]; j++) {
                uint16_t linkedService = service->linkedServices[j];

                for (size_t k = 0; k < j; k++) {
                    if (linkedService == service->linkedServices[k]) {
                        HAPLogServiceError(
                                &logObject,
                                service,
                                accessory,
                                "linkedServices entry 0x%016llX specified multiple times.",
                                (unsigned long long) service->linkedServices[k]);
                        return false;
                    }
                }

                bool found = false;
                for (size_t k = 0; accessory->services[k]; k++) {
                    const HAPService* otherService = accessory->services[k];

                    if (otherService->iid == linkedService) {
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    HAPLogServiceError(
                            &logObject,
                            service,
                            accessory,
                            "linkedServices entry 0x%016llX does not correspond to a specified service.",
                            (unsigned long long) linkedService);
                    return false;
                }
            }
        }

        bool allCharacteristicsHidden = true;
        if (service->characteristics) {
            for (size_t j = 0; service->characteristics[j]; j++) {
                const HAPBaseCharacteristic* characteristic = service->characteristics[j];

                // Validate characteristic.
                if (!characteristic->debugDescription) {
                    HAPLogServiceError(
                            &logObject,
                            service,
                            accessory,
                            "Characteristic 0x%016llX debugDescription is not set.",
                            (unsigned long long) characteristic->iid);
                    return false;
                }
                size_t numCharacteristicDebugDescriptionBytes = HAPStringGetNumBytes(characteristic->debugDescription);
                if (!HAPUTF8IsValidData(characteristic->debugDescription, numCharacteristicDebugDescriptionBytes)) {
                    HAPLogCharacteristicError(
                            &logObject,
                            characteristic,
                            service,
                            accessory,
                            "Characteristic debugDescription %s is not a valid UTF-8 string.",
                            characteristic->debugDescription);
                    return false;
                }

                if (characteristic->manufacturerDescription) {
                    size_t numManufacturerDescriptionBytes =
                            HAPStringGetNumBytes(HAPNonnull(characteristic->manufacturerDescription));
                    if (!HAPUTF8IsValidData(
                                HAPNonnull(characteristic->manufacturerDescription), numManufacturerDescriptionBytes)) {
                        HAPLogCharacteristicError(
                                &logObject,
                                characteristic,
                                service,
                                accessory,
                                "Characteristic manufacturerDescription %s is not a valid UTF-8 string.",
                                characteristic->manufacturerDescription);
                        return false;
                    }
                }

                allCharacteristicsHidden &= characteristic->properties.hidden;

#define BASE_CHARACTERISTIC_CHECKS(chr) \
    do { \
        /* readable. */ \
        if (chr->properties.readable && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as readable but no handleRead callback set."); \
            return false; \
        } \
\
        /* writable. */ \
        if (chr->properties.writable && !chr->callbacks.handleWrite) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as writable but no handleWrite callback set."); \
            return false; \
        } \
\
        /* supportsEventNotification. */ \
        if (chr->properties.supportsEventNotification && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as supportsEventNotification but no handleRead callback set."); \
            return false; \
        } \
\
        /* readRequiresAdminPermissions. */ \
        if (chr->properties.readRequiresAdminPermissions && !chr->properties.readable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as readRequiresAdminPermissions but not as readable."); \
            return false; \
        } \
\
        /* writeRequiresAdminPermissions. */ \
        if (chr->properties.writeRequiresAdminPermissions && !chr->properties.writable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as writeRequiresAdminPermissions but not as writable."); \
            return false; \
        } \
\
        /* readRequiresAdminPermissions, writeRequiresAdminPermissions. */ \
        if (HAPCharacteristicReadRequiresAdminPermissions(chr) && chr->properties.writable && \
            !HAPCharacteristicWriteRequiresAdminPermissions(chr)) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as readRequiresAdminPermissions and writable " \
                    "but not as writeRequiresAdminPermissions."); \
            return false; \
        } \
\
        /* requiresTimedWrite. */ \
        if (chr->properties.requiresTimedWrite && !chr->properties.writable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as requiresTimedWrite but not as writable."); \
            return false; \
        } \
\
        /* supportsAuthorizationData. */ \
        if (chr->properties.supportsAuthorizationData && !chr->properties.writable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as supportsAuthorizationData but not as writable."); \
            return false; \
        } \
\
        /* ip.supportsWriteResponse. */ \
        if (chr->properties.ip.supportsWriteResponse && !chr->properties.writable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ip.supportsWriteResponse but not as writable."); \
            return false; \
        } \
        if (chr->properties.ip.supportsWriteResponse && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ip.supportsWriteResponse but no handleRead callback set."); \
            return false; \
        } \
        if (chr->properties.ip.supportsWriteResponse && !chr->callbacks.handleWrite) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ip.supportsWriteResponse but no handleWrite callback set."); \
            return false; \
        } \
\
        /* ble.supportsBroadcastNotification */ \
        if (chr->properties.ble.supportsBroadcastNotification && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.supportsBroadcastNotification " \
                    "but no handleRead callback set."); \
            return false; \
        } \
\
        /* ble.supportsDisconnectedNotification */ \
        if (chr->properties.ble.supportsDisconnectedNotification && !chr->properties.readable) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.supportsDisconnectedNotification " \
                    "but not as readable."); \
            return false; \
        } \
        if (chr->properties.ble.supportsDisconnectedNotification && !chr->properties.supportsEventNotification) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.supportsDisconnectedNotification " \
                    "but not as supportsEventNotification."); \
            return false; \
        } \
        if (chr->properties.ble.supportsDisconnectedNotification && \
            !chr->properties.ble.supportsBroadcastNotification) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.supportsDisconnectedNotification " \
                    "but not as ble.supportsBroadcastNotification."); \
            return false; \
        } \
        if (chr->properties.ble.supportsDisconnectedNotification && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.supportsDisconnectedNotification " \
                    "but no handleRead callback set."); \
            return false; \
        } \
\
        /* ble.readableWithoutSecurity. */ \
        if (chr->properties.readable && !chr->callbacks.handleRead) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.readableWithoutSecurity " \
                    "but no handleRead callback set."); \
            return false; \
        } \
\
        /* ble.writableWithoutSecurity. */ \
        if (chr->properties.writable && !chr->callbacks.handleWrite) { \
            HAPLogCharacteristicError( \
                    &logObject, \
                    characteristic, \
                    service, \
                    accessory, \
                    "Characteristic marked as ble.writableWithoutSecurity " \
                    "but no handleWrite callback set."); \
            return false; \
        } \
    } while (0)
                switch (characteristic->format) {
                    case kHAPCharacteristicFormat_Data: {
                        const HAPDataCharacteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);
                    } break;
                    case kHAPCharacteristicFormat_Bool: {
                        const HAPBoolCharacteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);
                    } break;
                    case kHAPCharacteristicFormat_UInt8: {
                        const HAPUInt8Characteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        if (chr->constraints.minimumValue > chr->constraints.maximumValue) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %u / maximumValue = %u / stepValue = %u).",
                                    chr->constraints.minimumValue,
                                    chr->constraints.maximumValue,
                                    chr->constraints.stepValue);
                            return false;
                        }

                        const uint8_t* _Nullable const* _Nullable validValues = chr->constraints.validValues;
                        const HAPUInt8CharacteristicValidValuesRange* _Nullable const* _Nullable validValuesRanges =
                                chr->constraints.validValuesRanges;
                        if (validValues || validValuesRanges) {
                            if (!HAPUUIDIsAppleDefined(chr->characteristicType)) {
                                HAPLogCharacteristicError(
                                        &logObject,
                                        characteristic,
                                        service,
                                        accessory,
                                        "Only Apple-defined characteristics can specify "
                                        "validValues and validValuesRanges constraints.");
                                return false;
                            }
                            if (validValues) {
                                for (size_t k = 0; validValues[k]; k++) {
                                    if (k) {
                                        if (*validValues[k] <= *validValues[k - 1]) {
                                            HAPLogCharacteristicError(
                                                    &logObject,
                                                    characteristic,
                                                    service,
                                                    accessory,
                                                    "Characteristic validValues must be sorted in ascending order "
                                                    "(%u is listed before %u).",
                                                    *validValues[k - 1],
                                                    *validValues[k]);
                                            return false;
                                        }
                                    }
                                }
                            }
                            if (validValuesRanges) {
                                for (size_t k = 0; validValuesRanges[k]; k++) {
                                    if (validValuesRanges[k]->start > validValuesRanges[k]->end) {
                                        HAPLogCharacteristicError(
                                                &logObject,
                                                characteristic,
                                                service,
                                                accessory,
                                                "Characteristic validValuesRanges invalid ([%u ... %u]).",
                                                validValuesRanges[k]->start,
                                                validValuesRanges[k]->end);
                                        return false;
                                    }
                                    if (k) {
                                        if (validValuesRanges[k]->start < validValuesRanges[k - 1]->end) {
                                            HAPLogCharacteristicError(
                                                    &logObject,
                                                    characteristic,
                                                    service,
                                                    accessory,
                                                    "Characteristic validValuesRanges must be sorted in ascending "
                                                    "order "
                                                    "([%u ... %u] is listed before [%u ... %u]).",
                                                    validValuesRanges[k - 1]->start,
                                                    validValuesRanges[k - 1]->end,
                                                    validValuesRanges[k]->start,
                                                    validValuesRanges[k]->end);
                                            return false;
                                        }
                                    }
                                }
                            }
                        }
                    } break;
                    case kHAPCharacteristicFormat_UInt16: {
                        const HAPUInt16Characteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        if (chr->constraints.minimumValue > chr->constraints.maximumValue) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %u / maximumValue = %u / stepValue = %u).",
                                    chr->constraints.minimumValue,
                                    chr->constraints.maximumValue,
                                    chr->constraints.stepValue);
                            return false;
                        }
                    } break;
                    case kHAPCharacteristicFormat_UInt32: {
                        const HAPUInt32Characteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        if (chr->constraints.minimumValue > chr->constraints.maximumValue) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %lu / maximumValue = %lu / stepValue = %lu).",
                                    (unsigned long) chr->constraints.minimumValue,
                                    (unsigned long) chr->constraints.maximumValue,
                                    (unsigned long) chr->constraints.stepValue);
                            return false;
                        }
                    } break;
                    case kHAPCharacteristicFormat_UInt64: {
                        const HAPUInt64Characteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        if (chr->constraints.minimumValue > chr->constraints.maximumValue) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %llu / maximumValue = %llu / stepValue = %llu).",
                                    (unsigned long long) chr->constraints.minimumValue,
                                    (unsigned long long) chr->constraints.maximumValue,
                                    (unsigned long long) chr->constraints.stepValue);
                            return false;
                        }
                    } break;
                    case kHAPCharacteristicFormat_Int: {
                        const HAPIntCharacteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        if (chr->constraints.minimumValue > chr->constraints.maximumValue ||
                            chr->constraints.stepValue < 0) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %ld / maximumValue = %ld / stepValue = %ld).",
                                    (long) chr->constraints.minimumValue,
                                    (long) chr->constraints.maximumValue,
                                    (long) chr->constraints.stepValue);
                            return false;
                        }
                    } break;
                    case kHAPCharacteristicFormat_Float: {
                        const HAPFloatCharacteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);

                        float minimumValue = chr->constraints.minimumValue;
                        float maximumValue = chr->constraints.maximumValue;
                        float stepValue = chr->constraints.stepValue;
                        if ((!HAPFloatIsFinite(minimumValue) && !HAPFloatIsInfinite(minimumValue)) ||
                            (!HAPFloatIsFinite(maximumValue) && !HAPFloatIsInfinite(maximumValue)) ||
                            chr->constraints.minimumValue > chr->constraints.maximumValue ||
                            !HAPFloatIsFinite(stepValue) || chr->constraints.stepValue < 0) {
                            HAPLogCharacteristicError(
                                    &logObject,
                                    characteristic,
                                    service,
                                    accessory,
                                    "Characteristic constraints invalid "
                                    "(constraints: minimumValue = %g / maximumValue = %g / stepValue = %g).",
                                    (double) chr->constraints.minimumValue,
                                    (double) chr->constraints.maximumValue,
                                    (double) chr->constraints.stepValue);
                            return false;
                        }
                    } break;
                    case kHAPCharacteristicFormat_String: {
                        const HAPStringCharacteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);
                    } break;
                    case kHAPCharacteristicFormat_TLV8: {
                        const HAPTLV8Characteristic* chr = service->characteristics[j];
                        BASE_CHARACTERISTIC_CHECKS(chr);
                    } break;
                }
#undef BASE_CHARACTERISTIC_CHECKS
            }
        }

        // When all characteristics in a services are marked hidden then the service must also be marked as hidden.
        // See HomeKit Accessory Protocol Specification R14
        // Section 2.3.2.4 Hidden Service
        if (allCharacteristicsHidden && !service->properties.hidden) {
            HAPLogServiceError(
                    &logObject,
                    service,
                    accessory,
                    "Service must be marked hidden if all of its characteristics are marked hidden.");
            return false;
        }

        // iOS 11: The configuration attribute is only working on HAP Protocol Information service.
        if (service->properties.ble.supportsConfiguration &&
            !HAPUUIDAreEqual(service->serviceType, &kHAPServiceType_HAPProtocolInformation)) {
            HAPLogServiceError(
                    &logObject,
                    service,
                    accessory,
                    "Only the HAP Protocol Information service may support configuration.");
            return false;
        }
    }

    return true;
}