893 lines
36 KiB
C
893 lines
36 KiB
C
// Copyright (c) 2015-2019 The HomeKit ADK Contributors
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the “License”);
|
|
// you may not use this file except in compliance with the License.
|
|
// See [CONTRIBUTORS.md] for the list of HomeKit ADK project authors.
|
|
|
|
#include "HAP.h"
|
|
#include "HAPPlatformBLEPeripheralManager+Test.h"
|
|
#include "HAPPlatformServiceDiscovery+Test.h"
|
|
#include "HAPTestController.h"
|
|
|
|
#include "util_base64.h"
|
|
|
|
static const HAPLogObject logObject = { .subsystem = "com.apple.mfi.HomeKit.Core.Test", .category = "TestController" };
|
|
|
|
typedef struct {
|
|
HAPAccessoryServerInfo* _Nonnull serverInfo;
|
|
bool invalidData : 1;
|
|
bool foundCN : 1;
|
|
bool foundFF : 1;
|
|
bool foundID : 1;
|
|
bool foundMD : 1;
|
|
bool foundPV : 1;
|
|
bool foundSN : 1;
|
|
bool foundSF : 1;
|
|
bool foundCI : 1;
|
|
bool foundSH : 1;
|
|
} EnumerateHAPTXTRecordsContext;
|
|
|
|
static void EnumerateHAPTXTRecordsCallback(
|
|
void* _Nullable context,
|
|
HAPPlatformServiceDiscoveryRef _Nonnull serviceDiscovery,
|
|
const char* _Nonnull key,
|
|
const void* _Nonnull valueBytes,
|
|
size_t numValueBytes,
|
|
bool* _Nonnull shouldContinue) {
|
|
HAPPrecondition(context);
|
|
EnumerateHAPTXTRecordsContext* arguments = context;
|
|
HAPPrecondition(arguments->serverInfo);
|
|
HAPPrecondition(!arguments->invalidData);
|
|
HAPPrecondition(serviceDiscovery);
|
|
HAPPrecondition(key);
|
|
HAPPrecondition(valueBytes);
|
|
HAPPrecondition(shouldContinue);
|
|
HAPPrecondition(*shouldContinue);
|
|
|
|
HAPError err;
|
|
|
|
// See HomeKit Accessory Protocol Specification R14
|
|
// Section 6.4 Discovery
|
|
|
|
#define RETURN_ERROR() \
|
|
do { \
|
|
arguments->invalidData = true; \
|
|
*shouldContinue = false; \
|
|
return; \
|
|
} while (0)
|
|
|
|
#define PARSE_INT_OR_RETURN_ERROR(value, minValue, maxValue) \
|
|
do { \
|
|
err = HAPUInt64FromString(valueBytes, (value)); \
|
|
if (err) { \
|
|
HAPAssert(err == kHAPError_InvalidData); \
|
|
HAPLogError(&logObject, "Found malformed %s value (not a number).", key); \
|
|
RETURN_ERROR(); \
|
|
} \
|
|
if (*(value) < (minValue) || *(value) > (maxValue)) { \
|
|
HAPLogError(&logObject, "Found out of range %s value (%llu).", key, (unsigned long long) *(value)); \
|
|
RETURN_ERROR(); \
|
|
} \
|
|
} while (0)
|
|
|
|
if (HAPStringGetNumBytes(valueBytes) != numValueBytes) {
|
|
HAPLogError(&logObject, "Found malformed %s value (not a string).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
|
|
if (HAPStringAreEqual(key, "c#")) {
|
|
if (arguments->foundCN) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundCN = true;
|
|
|
|
uint64_t value;
|
|
PARSE_INT_OR_RETURN_ERROR(&value, 1, 65535);
|
|
arguments->serverInfo->configurationNumber = (uint32_t) value;
|
|
} else if (HAPStringAreEqual(key, "ff")) {
|
|
if (arguments->foundFF) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundFF = true;
|
|
|
|
uint64_t value;
|
|
PARSE_INT_OR_RETURN_ERROR(&value, 0x00, 0xff);
|
|
if ((value & 0x01u) == 0x01u) {
|
|
arguments->serverInfo->pairingFeatureFlags.supportsMFiHWAuth = true;
|
|
value &= ~0x01u;
|
|
}
|
|
if ((value & 0x02u) == 0x02u) {
|
|
arguments->serverInfo->pairingFeatureFlags.supportsMFiTokenAuth = true;
|
|
value &= ~0x02u;
|
|
}
|
|
if (value) {
|
|
HAPLog(&logObject, "Ignoring unknown %s flags: 0x%02x.", key, (uint8_t) value);
|
|
}
|
|
} else if (HAPStringAreEqual(key, "id")) {
|
|
if (arguments->foundID) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundID = true;
|
|
|
|
// XX:XX:XX:XX:XX:XX
|
|
const char* stringValue = valueBytes;
|
|
if (numValueBytes != 3 * 6 - 1) {
|
|
HAPLogError(&logObject, "Found malformed %s value (unexpected length).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
for (size_t i = 0; i < 5; i++) {
|
|
if (stringValue[3 * i + 2] != ':') {
|
|
HAPLogError(&logObject, "Found malformed %s value (separator missing).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
}
|
|
for (size_t i = 0; i < 6; i++) {
|
|
uint8_t n = 0;
|
|
char c = stringValue[3 * i + 0];
|
|
switch (c) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': {
|
|
n += (uint8_t)(0 + c - '0');
|
|
} break;
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'e':
|
|
case 'f': {
|
|
n += (uint8_t)(10 + c - 'a');
|
|
} break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
case 'E':
|
|
case 'F': {
|
|
n += (uint8_t)(10 + c - 'A');
|
|
} break;
|
|
default: {
|
|
HAPLogError(&logObject, "Found malformed %s value (unexpected character).", key);
|
|
}
|
|
RETURN_ERROR();
|
|
}
|
|
n *= 16;
|
|
c = stringValue[3 * i + 1];
|
|
switch (c) {
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': {
|
|
n += (uint8_t)(0 + c - '0');
|
|
} break;
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'd':
|
|
case 'e':
|
|
case 'f': {
|
|
n += (uint8_t)(10 + c - 'a');
|
|
} break;
|
|
case 'A':
|
|
case 'B':
|
|
case 'C':
|
|
case 'D':
|
|
case 'E':
|
|
case 'F': {
|
|
n += (uint8_t)(10 + c - 'A');
|
|
} break;
|
|
default: {
|
|
HAPLogError(&logObject, "Found malformed %s value (unexpected character).", key);
|
|
}
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->serverInfo->deviceID.bytes[i] = n;
|
|
}
|
|
} else if (HAPStringAreEqual(key, "md")) {
|
|
if (arguments->foundMD) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundMD = true;
|
|
|
|
if (numValueBytes >= sizeof arguments->serverInfo->model) {
|
|
HAPLogError(&logObject, "Found too long %s value (%zu bytes).", key, numValueBytes);
|
|
RETURN_ERROR();
|
|
}
|
|
HAPRawBufferCopyBytes(arguments->serverInfo->model, valueBytes, numValueBytes + 1);
|
|
} else if (HAPStringAreEqual(key, "pv")) {
|
|
if (arguments->foundPV) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundPV = true;
|
|
|
|
// X.X
|
|
const char* stringValue = valueBytes;
|
|
bool separatorAllowed = false;
|
|
bool majorRead = false;
|
|
uint8_t n = 0;
|
|
for (size_t i = 0; i < numValueBytes; i++) {
|
|
char c = stringValue[i];
|
|
switch (c) {
|
|
case '.': {
|
|
if (!separatorAllowed) {
|
|
HAPLogError(&logObject, "Found malformed %s value (separator at unexpected position).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
if (majorRead) {
|
|
HAPLogError(&logObject, "Found malformed %s value (too many separators).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
majorRead = true;
|
|
arguments->serverInfo->protocolVersion.major = n;
|
|
n = 0;
|
|
} break;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9': {
|
|
separatorAllowed = true;
|
|
if (n > UINT8_MAX / 10) {
|
|
HAPLogError(&logObject, "Found malformed %s value (version too large).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
n *= 10;
|
|
if (n > UINT8_MAX - (c - '0')) {
|
|
HAPLogError(&logObject, "Found malformed %s value (version too large).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
n += c - '0';
|
|
} break;
|
|
default: {
|
|
HAPLogError(&logObject, "Found malformed %s value (unexpected character).", key);
|
|
}
|
|
RETURN_ERROR();
|
|
}
|
|
}
|
|
if (!separatorAllowed) {
|
|
HAPLogError(&logObject, "Found malformed %s value (not ending in number).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
if (!majorRead) {
|
|
arguments->serverInfo->protocolVersion.major = n;
|
|
n = 0;
|
|
}
|
|
arguments->serverInfo->protocolVersion.minor = n;
|
|
} else if (HAPStringAreEqual(key, "s#")) {
|
|
if (arguments->foundSN) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundSN = true;
|
|
|
|
if (!HAPStringAreEqual(valueBytes, "1")) {
|
|
HAPLogError(&logObject, "Found unexpected %s value (must be 1).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->serverInfo->stateNumber = 1;
|
|
} else if (HAPStringAreEqual(key, "sf")) {
|
|
if (arguments->foundSF) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundSF = true;
|
|
|
|
uint64_t value;
|
|
PARSE_INT_OR_RETURN_ERROR(&value, 0x00, 0xff);
|
|
if ((value & 0x01u) == 0x01u) {
|
|
arguments->serverInfo->statusFlags.isNotPaired = true;
|
|
value &= ~0x01u;
|
|
}
|
|
if ((value & 0x04u) == 0x04u) {
|
|
arguments->serverInfo->statusFlags.hasProblem = true;
|
|
value &= ~0x04u;
|
|
}
|
|
if (value) {
|
|
HAPLog(&logObject, "Ignoring unknown %s flags: 0x%02x.", key, (uint8_t) value);
|
|
}
|
|
} else if (HAPStringAreEqual(key, "ci")) {
|
|
if (arguments->foundCI) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundCI = true;
|
|
|
|
uint64_t value;
|
|
PARSE_INT_OR_RETURN_ERROR(&value, 1, 65535);
|
|
if (value > (HAPAccessoryCategory) -1) {
|
|
HAPLog(&logObject, "Found unexpected %s value: %u.", key, (uint16_t) value);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->serverInfo->category = 0;
|
|
switch ((HAPAccessoryCategory) value) {
|
|
case kHAPAccessoryCategory_BridgedAccessory: {
|
|
HAPLog(&logObject, "Found unexpected %s value: %u.", key, (uint16_t) value);
|
|
}
|
|
RETURN_ERROR();
|
|
case kHAPAccessoryCategory_Other:
|
|
case kHAPAccessoryCategory_Bridges:
|
|
case kHAPAccessoryCategory_Fans:
|
|
case kHAPAccessoryCategory_GarageDoorOpeners:
|
|
case kHAPAccessoryCategory_Lighting:
|
|
case kHAPAccessoryCategory_Locks:
|
|
case kHAPAccessoryCategory_Outlets:
|
|
case kHAPAccessoryCategory_Switches:
|
|
case kHAPAccessoryCategory_Thermostats:
|
|
case kHAPAccessoryCategory_Sensors:
|
|
case kHAPAccessoryCategory_SecuritySystems:
|
|
case kHAPAccessoryCategory_Doors:
|
|
case kHAPAccessoryCategory_Windows:
|
|
case kHAPAccessoryCategory_WindowCoverings:
|
|
case kHAPAccessoryCategory_ProgrammableSwitches:
|
|
case kHAPAccessoryCategory_RangeExtenders:
|
|
case kHAPAccessoryCategory_IPCameras:
|
|
case kHAPAccessoryCategory_AirPurifiers:
|
|
case kHAPAccessoryCategory_Heaters:
|
|
case kHAPAccessoryCategory_AirConditioners:
|
|
case kHAPAccessoryCategory_Humidifiers:
|
|
case kHAPAccessoryCategory_Dehumidifiers:
|
|
case kHAPAccessoryCategory_Sprinklers:
|
|
case kHAPAccessoryCategory_Faucets:
|
|
case kHAPAccessoryCategory_ShowerSystems: {
|
|
arguments->serverInfo->category = (HAPAccessoryCategory) value;
|
|
} break;
|
|
}
|
|
if (!arguments->serverInfo->category) {
|
|
HAPLog(&logObject, "Found unexpected %s value: %u.", key, (uint16_t) value);
|
|
RETURN_ERROR();
|
|
}
|
|
} else if (HAPStringAreEqual(key, "sh")) {
|
|
if (arguments->foundSH) {
|
|
HAPLogError(&logObject, "Found duplicate %s key.", key);
|
|
RETURN_ERROR();
|
|
}
|
|
arguments->foundSH = true;
|
|
|
|
if (numValueBytes != util_base64_encoded_len(sizeof arguments->serverInfo->setupHash.bytes + 1)) {
|
|
HAPLogError(&logObject, "Found malformed %s value (unexpected length).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
size_t numDecodedBytes;
|
|
err = util_base64_decode(
|
|
valueBytes,
|
|
numValueBytes,
|
|
arguments->serverInfo->setupHash.bytes,
|
|
sizeof arguments->serverInfo->setupHash.bytes,
|
|
&numDecodedBytes);
|
|
HAPAssert(err != kHAPError_OutOfResources);
|
|
if (err) {
|
|
HAPAssert(err == kHAPError_InvalidData);
|
|
HAPLogError(&logObject, "Found malformed %s value (not in base64 format).", key);
|
|
RETURN_ERROR();
|
|
}
|
|
HAPAssert(numDecodedBytes == sizeof arguments->serverInfo->setupHash.bytes);
|
|
arguments->serverInfo->setupHash.isSet = true;
|
|
} else {
|
|
HAPLog(&logObject, "Ignoring unknown %s key.", key);
|
|
}
|
|
|
|
#undef PARSE_INT_OR_RETURN_ERROR
|
|
#undef RETURN_ERROR
|
|
}
|
|
|
|
HAPError HAPDiscoverIPAccessoryServer(
|
|
HAPPlatformServiceDiscoveryRef _Nonnull serviceDiscovery,
|
|
HAPAccessoryServerInfo* _Nonnull serverInfo,
|
|
HAPNetworkPort* _Nonnull serverPort) {
|
|
HAPPrecondition(serviceDiscovery);
|
|
HAPPrecondition(serverInfo);
|
|
HAPPrecondition(serverPort);
|
|
|
|
HAPRawBufferZero(serverInfo, sizeof *serverInfo);
|
|
*serverPort = 0;
|
|
|
|
if (!HAPPlatformServiceDiscoveryIsAdvertising(serviceDiscovery)) {
|
|
HAPLog(&logObject, "IP accessory server is not advertising.");
|
|
return kHAPError_InvalidState;
|
|
}
|
|
|
|
// See HomeKit Accessory Protocol Specification R14
|
|
// Section 6.4 Discovery
|
|
{
|
|
const char* value = HAPPlatformServiceDiscoveryGetName(serviceDiscovery);
|
|
size_t numValueBytes = HAPStringGetNumBytes(value);
|
|
if (numValueBytes >= sizeof serverInfo->name) {
|
|
HAPLogError(&logObject, "Found too long name (%zu bytes).", numValueBytes);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
HAPRawBufferCopyBytes(serverInfo->name, value, numValueBytes + 1);
|
|
}
|
|
if (!HAPStringAreEqual(HAPPlatformServiceDiscoveryGetProtocol(serviceDiscovery), "_hap._tcp")) {
|
|
HAPLog(&logObject, "IP accessory server is not advertising HAP service.");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
*serverPort = HAPPlatformServiceDiscoveryGetPort(serviceDiscovery);
|
|
|
|
// Process TXT records.
|
|
EnumerateHAPTXTRecordsContext context = { .serverInfo = serverInfo };
|
|
HAPPlatformServiceDiscoveryEnumerateTXTRecords(serviceDiscovery, EnumerateHAPTXTRecordsCallback, &context);
|
|
if (context.invalidData) {
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundCN) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "c#");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundFF) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "ff");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundID) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "id");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundMD) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "md");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundPV) {
|
|
context.serverInfo->protocolVersion.major = 1;
|
|
context.serverInfo->protocolVersion.minor = 0;
|
|
}
|
|
if (!context.foundSN) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "s#");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundSF) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "sf");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundCI) {
|
|
HAPLogError(&logObject, "IP accessory server is not advertising %s key.", "ci");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!context.foundSH) {
|
|
context.serverInfo->setupHash.isSet = false;
|
|
}
|
|
|
|
return kHAPError_None;
|
|
}
|
|
|
|
HAPError HAPDiscoverBLEAccessoryServer(
|
|
HAPPlatformBLEPeripheralManagerRef _Nonnull blePeripheralManager,
|
|
HAPAccessoryServerInfo* _Nonnull serverInfo,
|
|
HAPPlatformBLEPeripheralManagerDeviceAddress* _Nonnull deviceAddress) {
|
|
HAPPrecondition(blePeripheralManager);
|
|
HAPPrecondition(serverInfo);
|
|
HAPPrecondition(deviceAddress);
|
|
|
|
HAPError err;
|
|
|
|
HAPRawBufferZero(serverInfo, sizeof *serverInfo);
|
|
HAPRawBufferZero(deviceAddress, sizeof *deviceAddress);
|
|
|
|
if (!HAPPlatformBLEPeripheralManagerIsAdvertising(blePeripheralManager)) {
|
|
HAPLog(&logObject, "BLE accessory server is not advertising.");
|
|
return kHAPError_InvalidState;
|
|
}
|
|
|
|
// See HomeKit Accessory Protocol Specification R14
|
|
// Section 7.4.2.1 HAP BLE Regular Advertisement Format
|
|
uint8_t advertisingBytes[31];
|
|
size_t numAdvertisingBytes;
|
|
uint8_t scanResponseBytes[31];
|
|
size_t numScanResponseBytes;
|
|
err = HAPPlatformBLEPeripheralManagerGetAdvertisingData(
|
|
blePeripheralManager,
|
|
advertisingBytes,
|
|
sizeof advertisingBytes,
|
|
&numAdvertisingBytes,
|
|
scanResponseBytes,
|
|
sizeof scanResponseBytes,
|
|
&numScanResponseBytes);
|
|
HAPAssert(!err);
|
|
|
|
// See Bluetooth Core Specification Version 5
|
|
// Vol 3 Part C Section 11 Advertising and Scan Response Data Format
|
|
|
|
// Process advertising data.
|
|
bool foundFlags = false;
|
|
bool foundManufacturerData = false;
|
|
bool foundShortenedName = false;
|
|
bool foundCompleteName = false;
|
|
for (size_t i = 0; i < numAdvertisingBytes;) {
|
|
uint8_t length = advertisingBytes[i++];
|
|
if (numAdvertisingBytes < length) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (invalid length).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (AD type missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t adType = advertisingBytes[i++];
|
|
length--;
|
|
switch (adType) {
|
|
case 0x01: {
|
|
// Flags.
|
|
// See Bluetooth Core Specification Supplement Version 7
|
|
// Section 1.3 Flags
|
|
if (length != 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Invalid %s length).", "Flags");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t flags = advertisingBytes[i];
|
|
if ((flags & (1 << 0)) != (0 << 0)) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (LE Limited Discoverable Mode is set).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if ((flags & (1 << 1)) != (1 << 1)) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (LE General Discoverable Mode is not set).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (foundFlags) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Flags).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
foundFlags = true;
|
|
} break;
|
|
case 0x08: {
|
|
// Shortened Local Name.
|
|
// See Bluetooth Core Specification Supplement Version 7
|
|
// Section 1.2 Local Name
|
|
if (length >= sizeof serverInfo->name) {
|
|
HAPLogError(
|
|
&logObject, "BLE advertising data invalid (Invalid %s length).", "Shortened Local Name");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (foundCompleteName) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (foundShortenedName) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Shortened Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
foundShortenedName = true;
|
|
|
|
HAPRawBufferZero(serverInfo->name, sizeof serverInfo->name);
|
|
HAPRawBufferCopyBytes(serverInfo->name, &advertisingBytes[i], length);
|
|
if (length != HAPStringGetNumBytes(serverInfo->name)) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Shortened Local Name contains NULL bytes).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
} break;
|
|
case 0x09: {
|
|
// Complete Local Name.
|
|
// See Bluetooth Core Specification Supplement Version 7
|
|
// Section 1.2 Local Name
|
|
if (length >= sizeof serverInfo->name) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Invalid %s length).", "Complete Local Name");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (foundShortenedName) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (foundCompleteName) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Complete Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
foundCompleteName = true;
|
|
|
|
if (foundShortenedName) {
|
|
if (HAPStringGetNumBytes(serverInfo->name) > length) {
|
|
HAPLogError(
|
|
&logObject,
|
|
"BLE advertising data invalid (Complete Local Name shorter than Shortened Local "
|
|
"Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!HAPRawBufferAreEqual(
|
|
serverInfo->name, &advertisingBytes[i], HAPStringGetNumBytes(serverInfo->name))) {
|
|
HAPLogError(
|
|
&logObject,
|
|
"BLE advertising data invalid "
|
|
"(Complete Local Name does not start with Shortened Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
}
|
|
|
|
HAPRawBufferZero(serverInfo->name, sizeof serverInfo->name);
|
|
HAPRawBufferCopyBytes(serverInfo->name, &advertisingBytes[i], length);
|
|
if (length != HAPStringGetNumBytes(serverInfo->name)) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Complete Local Name contains NULL bytes).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
}
|
|
case 0xFF: {
|
|
// Manufacturer Specific Data.
|
|
// See Bluetooth Core Specification Supplement Version 7
|
|
// Section 1.4 Manufacturer Specific Data
|
|
if (length < 2) {
|
|
HAPLogError(
|
|
&logObject,
|
|
"BLE advertising data invalid (Invalid %s length).",
|
|
"Manufacturer Specific Data");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint16_t coID = HAPReadLittleUInt16(&advertisingBytes[i]);
|
|
i += 2;
|
|
length -= 2;
|
|
if (coID != 0x004C) {
|
|
HAPLog(&logObject,
|
|
"Ignoring unknown Manufacturer Specific Data from company with ID 0x%04X.",
|
|
coID);
|
|
break;
|
|
}
|
|
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Apple, Inc. Type missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t type = advertisingBytes[i++];
|
|
length--;
|
|
if (type != 0x06) {
|
|
HAPLog(&logObject, "Ignoring unknown Apple, Inc. Specific Data with Type 0x%02X.", type);
|
|
break;
|
|
}
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Apple, Inc. SubTypeLength missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t subTypeLength = advertisingBytes[i++];
|
|
length--;
|
|
uint8_t subType = (uint8_t)((subTypeLength >> 5) & ((1 << 0) | (1 << 1) | (1 << 2)));
|
|
if (subType != 1) {
|
|
HAPLog(&logObject,
|
|
"Ignoring unknown Apple, Inc. Specific Data with Type 0x%02X / SubType 0x%02X.",
|
|
type,
|
|
subType);
|
|
break;
|
|
}
|
|
if ((subTypeLength & ((1 << 0) | (1 << 1) | (1 << 2) | (1 << 3) | (1 << 4))) != length) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Unexpected Apple, Inc. SubTypeLength).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (foundManufacturerData) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Duplicate Manufacturer Data).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
foundManufacturerData = true;
|
|
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (SF missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t sf = advertisingBytes[i++];
|
|
length--;
|
|
serverInfo->statusFlags.isNotPaired = (sf & (1 << 0)) == (1 << 0);
|
|
|
|
if (length < sizeof deviceAddress->bytes) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Device ID missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
HAPRawBufferCopyBytes(deviceAddress->bytes, &advertisingBytes[i], sizeof deviceAddress->bytes);
|
|
i += sizeof deviceAddress->bytes;
|
|
length -= sizeof deviceAddress->bytes;
|
|
|
|
if (length < 2) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (ACID missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint16_t acid = HAPReadLittleUInt16(&advertisingBytes[i]);
|
|
i += 2;
|
|
length -= 2;
|
|
if (acid > (HAPAccessoryCategory) -1) {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected ACID value: %u).", acid);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
serverInfo->category = 0;
|
|
switch ((HAPAccessoryCategory) acid) {
|
|
case kHAPAccessoryCategory_BridgedAccessory: {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected ACID value: %u).", acid);
|
|
}
|
|
return kHAPError_InvalidData;
|
|
case kHAPAccessoryCategory_Other:
|
|
case kHAPAccessoryCategory_Bridges:
|
|
case kHAPAccessoryCategory_Fans:
|
|
case kHAPAccessoryCategory_GarageDoorOpeners:
|
|
case kHAPAccessoryCategory_Lighting:
|
|
case kHAPAccessoryCategory_Locks:
|
|
case kHAPAccessoryCategory_Outlets:
|
|
case kHAPAccessoryCategory_Switches:
|
|
case kHAPAccessoryCategory_Thermostats:
|
|
case kHAPAccessoryCategory_Sensors:
|
|
case kHAPAccessoryCategory_SecuritySystems:
|
|
case kHAPAccessoryCategory_Doors:
|
|
case kHAPAccessoryCategory_Windows:
|
|
case kHAPAccessoryCategory_WindowCoverings:
|
|
case kHAPAccessoryCategory_ProgrammableSwitches:
|
|
case kHAPAccessoryCategory_RangeExtenders:
|
|
case kHAPAccessoryCategory_IPCameras:
|
|
case kHAPAccessoryCategory_AirPurifiers:
|
|
case kHAPAccessoryCategory_Heaters:
|
|
case kHAPAccessoryCategory_AirConditioners:
|
|
case kHAPAccessoryCategory_Humidifiers:
|
|
case kHAPAccessoryCategory_Dehumidifiers:
|
|
case kHAPAccessoryCategory_Sprinklers:
|
|
case kHAPAccessoryCategory_Faucets:
|
|
case kHAPAccessoryCategory_ShowerSystems: {
|
|
serverInfo->category = (HAPAccessoryCategory) acid;
|
|
} break;
|
|
}
|
|
if (!serverInfo->category) {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected ACID value: %u).", acid);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (length < 2) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (GSN missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint16_t gsn = HAPReadLittleUInt16(&advertisingBytes[i]);
|
|
i += 2;
|
|
length -= 2;
|
|
if (!gsn) {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected GSN value: %u).", gsn);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
serverInfo->stateNumber = gsn;
|
|
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (CN missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t cn = advertisingBytes[i++];
|
|
length--;
|
|
if (!cn) {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected CN value: %u).", cn);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
serverInfo->configurationNumber = cn;
|
|
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (CV missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t cv = advertisingBytes[i++];
|
|
length--;
|
|
if (cv != 2) {
|
|
HAPLog(&logObject, "BLE advertising data invalid (unexpected CV value: %u).", cv);
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (length) {
|
|
if (length < sizeof serverInfo->setupHash.bytes) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (SH missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
HAPRawBufferCopyBytes(
|
|
serverInfo->setupHash.bytes, &advertisingBytes[i], sizeof serverInfo->setupHash.bytes);
|
|
i += sizeof serverInfo->setupHash.bytes;
|
|
length -= sizeof serverInfo->setupHash.bytes;
|
|
serverInfo->setupHash.isSet = true;
|
|
}
|
|
|
|
if (length) {
|
|
HAPLog(&logObject, "Ignoring extra data in BLE Manufacturer Data (%u bytes).", length);
|
|
}
|
|
} break;
|
|
default: {
|
|
HAPLog(&logObject, "Ignoring unknown AD type in BLE advertising data: %u.", adType);
|
|
} break;
|
|
}
|
|
i += length;
|
|
}
|
|
if (!foundFlags) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Flags not found).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!foundManufacturerData) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Manufacturer Data not found).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!foundCompleteName && !foundShortenedName) {
|
|
HAPLogError(&logObject, "BLE advertising data invalid (Local Name not found).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
// Process scan response data.
|
|
bool foundName = foundCompleteName;
|
|
for (size_t i = 0; i < numScanResponseBytes;) {
|
|
uint8_t length = scanResponseBytes[i++];
|
|
if (numScanResponseBytes < length) {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (invalid length).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (length < 1) {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (AD type missing).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
uint8_t adType = scanResponseBytes[i++];
|
|
length--;
|
|
switch (adType) {
|
|
case 0x01: {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (Contains Flags).");
|
|
}
|
|
return kHAPError_InvalidData;
|
|
case 0x08:
|
|
case 0x09: {
|
|
// Local Name.
|
|
// See Bluetooth Core Specification Supplement Version 7
|
|
// Section 1.2 Local Name
|
|
if (length >= sizeof serverInfo->name) {
|
|
HAPLogError(
|
|
&logObject, "BLE scan response data invalid (Invalid %s length).", "Complete Local Name");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
if (foundName) {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (Duplicate Complete Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
foundName = true;
|
|
|
|
HAPAssert(foundShortenedName);
|
|
if (HAPStringGetNumBytes(serverInfo->name) > length) {
|
|
HAPLogError(
|
|
&logObject,
|
|
"BLE scan response data invalid (Local Name shorter than Shortened Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
if (!HAPRawBufferAreEqual(
|
|
serverInfo->name, &scanResponseBytes[i], HAPStringGetNumBytes(serverInfo->name))) {
|
|
HAPLogError(
|
|
&logObject,
|
|
"BLE scan response data invalid "
|
|
"(Local Name does not start with Shortened Local Name).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
HAPRawBufferZero(serverInfo->name, sizeof serverInfo->name);
|
|
HAPRawBufferCopyBytes(serverInfo->name, &scanResponseBytes[i], length);
|
|
if (length != HAPStringGetNumBytes(serverInfo->name)) {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (Local Name contains NULL bytes).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
} break;
|
|
case 0xFF: {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (Contains Manufacturer Data).");
|
|
}
|
|
return kHAPError_InvalidData;
|
|
default: {
|
|
HAPLog(&logObject, "Ignoring unknown AD type in BLE scan response data: %u.", adType);
|
|
} break;
|
|
}
|
|
i += length;
|
|
}
|
|
if (!foundName) {
|
|
HAPLogError(&logObject, "BLE scan response data invalid (Local Name not found).");
|
|
return kHAPError_InvalidData;
|
|
}
|
|
|
|
return kHAPError_None;
|
|
}
|