diff --git a/internal/platform/implementation/apple/BUILD b/internal/platform/implementation/apple/BUILD index ef78432a..7563076c 100644 --- a/internal/platform/implementation/apple/BUILD +++ b/internal/platform/implementation/apple/BUILD @@ -24,6 +24,7 @@ objc_library( name = "apple", srcs = [ "ble.mm", + "ble_utils.mm", "bluetooth_adapter.mm", "crypto.mm", "device_info.mm", @@ -38,6 +39,7 @@ objc_library( ], hdrs = [ "ble.h", + "ble_utils.h", "bluetooth_adapter.h", "device_info.h", "log_message.h", diff --git a/internal/platform/implementation/apple/Tests/BUILD b/internal/platform/implementation/apple/Tests/BUILD index 09a087c5..4a480bde 100644 --- a/internal/platform/implementation/apple/Tests/BUILD +++ b/internal/platform/implementation/apple/Tests/BUILD @@ -24,6 +24,7 @@ objc_library( testonly = True, srcs = [ "GNCBLEGATTCharacteristicTest.mm", + "GNCBLEUtilsTest.mm", "GNCBleTest.mm", "GNCBluetoothAdapterTest.mm", "GNCCryptoTest.mm", diff --git a/internal/platform/implementation/apple/Tests/GNCBLEUtilsTest.mm b/internal/platform/implementation/apple/Tests/GNCBLEUtilsTest.mm new file mode 100644 index 00000000..4434a2b2 --- /dev/null +++ b/internal/platform/implementation/apple/Tests/GNCBLEUtilsTest.mm @@ -0,0 +1,198 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import +#import + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h" +#import "internal/platform/implementation/apple/ble_utils.h" + +#include "internal/platform/implementation/ble_v2.h" + +using GattCharacteristic = ::nearby::api::ble_v2::GattCharacteristic; +using Property = ::nearby::api::ble_v2::GattCharacteristic::Property; +using Permission = ::nearby::api::ble_v2::GattCharacteristic::Permission; +using WriteType = ::nearby::api::ble_v2::GattClient::WriteType; +using Uuid = ::nearby::Uuid; + +@interface GNCBLEUtilsTest : XCTestCase +@end + +@implementation GNCBLEUtilsTest + +#pragma mark - C++ to Objective-C + +- (void)testCBUUID16FromCPP { + CBUUID *expected = [CBUUID UUIDWithString:@"FEF3"]; + CBUUID *actual = ::nearby::apple::CBUUID16FromCPP(Uuid(0x0000FEF300001000, 0x800000805F9B34FB)); + XCTAssertEqualObjects(actual.UUIDString, expected.UUIDString); +} + +- (void)testCBUUID128FromCPP { + CBUUID *expected = [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"]; + CBUUID *actual = ::nearby::apple::CBUUID128FromCPP(Uuid(0x0000FEF300001000, 0x800000805F9B34FB)); + XCTAssertEqualObjects(actual.UUIDString, expected.UUIDString); +} + +- (void)testCBCharacteristicWriteTypeFromCPPWithResponse { + CBCharacteristicWriteType expected = CBCharacteristicWriteWithResponse; + CBCharacteristicWriteType actual = + ::nearby::apple::CBCharacteristicWriteTypeFromCPP(WriteType::kWithResponse); + XCTAssertEqual(actual, expected); +} + +- (void)testCBCharacteristicWriteTypeFromCPPWithoutResponse { + CBCharacteristicWriteType expected = CBCharacteristicWriteWithoutResponse; + CBCharacteristicWriteType actual = + ::nearby::apple::CBCharacteristicWriteTypeFromCPP(WriteType::kWithoutResponse); + XCTAssertEqual(actual, expected); +} + +- (void)testCBCharacteristicPropertiesFromCPPEmptyProperty { + CBCharacteristicProperties expected = 0; + CBCharacteristicProperties actual = + ::nearby::apple::CBCharacteristicPropertiesFromCPP(Property::kNone); + XCTAssertEqual(actual, expected); +} + +- (void)testCBCharacteristicPropertiesFromCPPSingleProperty { + CBCharacteristicProperties expected = CBCharacteristicPropertyRead; + CBCharacteristicProperties actual = + ::nearby::apple::CBCharacteristicPropertiesFromCPP(Property::kRead); + XCTAssertEqual(actual, expected); +} + +- (void)testCBCharacteristicPropertiesFromCPPMultipleProperties { + CBCharacteristicProperties expected = CBCharacteristicPropertyWrite | + CBCharacteristicPropertyNotify | + CBCharacteristicPropertyIndicate; + CBCharacteristicProperties actual = ::nearby::apple::CBCharacteristicPropertiesFromCPP( + Property::kWrite | Property::kNotify | Property::kIndicate); + XCTAssertEqual(actual, expected); +} + +- (void)testCBAttributePermissionsFromCPPEmptyPermission { + CBAttributePermissions expected = 0; + CBAttributePermissions actual = ::nearby::apple::CBAttributePermissionsFromCPP(Permission::kNone); + XCTAssertEqual(actual, expected); +} + +- (void)testCBAttributePermissionsFromCPPSinglePermission { + CBAttributePermissions expected = CBAttributePermissionsReadable; + CBAttributePermissions actual = + ::nearby::apple::CBAttributePermissionsFromCPP(Permission::kRead); + XCTAssertEqual(actual, expected); +} + +- (void)testCBAttributePermissionsFromCPPMultiplePermissions { + CBAttributePermissions expected = + CBAttributePermissionsReadable | CBAttributePermissionsWriteable; + CBAttributePermissions actual = + ::nearby::apple::CBAttributePermissionsFromCPP(Permission::kRead | Permission::kWrite); + XCTAssertEqual(actual, expected); +} + +- (void)testObjCGATTCharacteristicFromCPP { + CBUUID *characteristicUUID = [CBUUID UUIDWithString:@"00000000-0000-3000-8000-000000000000"]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"]; + CBAttributePermissions permissions = CBAttributePermissionsReadable; + CBCharacteristicProperties properties = CBCharacteristicPropertyRead; + + GattCharacteristic characteristic = {Uuid(0x0000000000003000, 0x8000000000000000), + Uuid(0x0000FEF300001000, 0x800000805F9B34FB), + Permission::kRead, Property::kRead}; + + GNCBLEGATTCharacteristic *actual = ::nearby::apple::ObjCGATTCharacteristicFromCPP(characteristic); + XCTAssertEqualObjects(actual.characteristicUUID, characteristicUUID); + XCTAssertEqualObjects(actual.serviceUUID, serviceUUID); + XCTAssertEqual(actual.permissions, permissions); + XCTAssertEqual(actual.properties, properties); +} + +#pragma mark - Objective-C to C++ + +- (void)testCPPUUIDFromObjC16Bit { + Uuid expected = Uuid(0x0000FEF300001000, 0x800000805F9B34FB); + Uuid actual = ::nearby::apple::CPPUUIDFromObjC([CBUUID UUIDWithString:@"FEF3"]); + XCTAssertEqualObjects(@(std::string(actual).c_str()), @(std::string(expected).c_str())); +} + +- (void)testCPPUUIDFromObjC128Bit { + Uuid expected = Uuid(0x0000FEF300001000, 0x800000805F9B34FB); + Uuid actual = ::nearby::apple::CPPUUIDFromObjC( + [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"]); + XCTAssertEqualObjects(@(std::string(actual).c_str()), @(std::string(expected).c_str())); +} + +- (void)testCPPGATTCharacteristicPropertyFromObjCEmptyProperty { + Property expected = Property::kNone; + Property actual = ::nearby::apple::CPPGATTCharacteristicPropertyFromObjC(0); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicPropertyFromObjCSingleProperty { + Property expected = Property::kRead; + Property actual = + ::nearby::apple::CPPGATTCharacteristicPropertyFromObjC(CBCharacteristicPropertyRead); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicPropertyFromObjCMultipleProperties { + Property expected = Property::kWrite | Property::kNotify | Property::kIndicate; + Property actual = ::nearby::apple::CPPGATTCharacteristicPropertyFromObjC( + CBCharacteristicPropertyWrite | CBCharacteristicPropertyNotify | + CBCharacteristicPropertyIndicate); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicPermissionFromObjCEmptyPermission { + Permission expected = Permission::kNone; + Permission actual = ::nearby::apple::CPPGATTCharacteristicPermissionFromObjC(0); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicPermissionFromObjCSinglePermission { + Permission expected = Permission::kRead; + Permission actual = + ::nearby::apple::CPPGATTCharacteristicPermissionFromObjC(CBAttributePermissionsReadable); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicPermissionFromObjCMultiplePermissions { + Permission expected = Permission::kRead | Permission::kWrite; + Permission actual = ::nearby::apple::CPPGATTCharacteristicPermissionFromObjC( + CBAttributePermissionsReadable | CBAttributePermissionsWriteable); + XCTAssertEqual(actual, expected); +} + +- (void)testCPPGATTCharacteristicFromObjC { + CBUUID *characteristicUUID = [CBUUID UUIDWithString:@"00000000-0000-3000-8000-000000000000"]; + CBUUID *serviceUUID = [CBUUID UUIDWithString:@"0000FEF3-0000-1000-8000-00805F9B34FB"]; + CBAttributePermissions permissions = CBAttributePermissionsReadable; + CBCharacteristicProperties properties = CBCharacteristicPropertyRead; + + GNCBLEGATTCharacteristic *characteristic = + [[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID + serviceUUID:serviceUUID + permissions:permissions + properties:properties]; + + GattCharacteristic actual = ::nearby::apple::CPPGATTCharacteristicFromObjC(characteristic); + XCTAssertEqual(actual.uuid, Uuid(0x0000000000003000, 0x8000000000000000)); + XCTAssertEqual(actual.service_uuid, Uuid(0x0000FEF300001000, 0x800000805F9B34FB)); + XCTAssertEqual(actual.permission, Permission::kRead); + XCTAssertEqual(actual.property, Property::kRead); +} + +@end diff --git a/internal/platform/implementation/apple/ble_utils.h b/internal/platform/implementation/apple/ble_utils.h new file mode 100644 index 00000000..209298fb --- /dev/null +++ b/internal/platform/implementation/apple/ble_utils.h @@ -0,0 +1,82 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Note: File language is detected using heuristics. Many Objective-C++ headers are incorrectly +// classified as C++ resulting in invalid linter errors. The use of "NSArray" and other Foundation +// classes like "NSData", "NSDictionary" and "NSUUID" are highly weighted for Objective-C and +// Objective-C++ scores. Oddly, "#import " does not contribute any points. +// This comment alone should be enough to trick the IDE in to believing this is actually some sort +// of Objective-C file. See: cs/google3/devtools/search/lang/recognize_language_classifiers_data + +#import +#import + +#include "internal/platform/implementation/ble_v2.h" + +@class GNCBLEGATTCharacteristic; + +namespace nearby { +namespace apple { + +/** Converts a C++ UUID to a 16-bit CoreBluetooth UUID. */ +CBUUID *CBUUID16FromCPP(const Uuid &uuid); + +/** Converts a C++ UUID to a 128-bit CoreBluetooth UUID. */ +CBUUID *CBUUID128FromCPP(const Uuid &uuid); + +/** Converts a C++ write type enum to a CoreBluetooth write type. */ +CBCharacteristicWriteType CBCharacteristicWriteTypeFromCPP(api::ble_v2::GattClient::WriteType wt); + +/** + * Converts a C++ permission enum to CoreBluetooth permissions. + * + * This conversion keeps multiple permission values intact. + */ +CBAttributePermissions CBAttributePermissionsFromCPP(api::ble_v2::GattCharacteristic::Permission p); + +/** + * Converts a C++ property enum to CoreBluetooth properties. + * + * This conversion keeps multiple property values intact. + */ +CBCharacteristicProperties CBCharacteristicPropertiesFromCPP( + api::ble_v2::GattCharacteristic::Property p); + +/** Converts a C++ characteristic to an Objective-C characteristic. */ +GNCBLEGATTCharacteristic *ObjCGATTCharacteristicFromCPP(const api::ble_v2::GattCharacteristic &c); + +/** Converts a 16 or 128 bit CoreBluetooth UUID to a C++ UUID. */ +Uuid CPPUUIDFromObjC(CBUUID *uuid); + +/** + * Converts CoreBluetooth permissions to a C++ permission enum. + * + * This conversion keeps multiple permission values intact. + */ +api::ble_v2::GattCharacteristic::Permission CPPGATTCharacteristicPermissionFromObjC( + CBAttributePermissions p); + +/** + * Converts CoreBluetooth properties to a C++ property enum. + * + * This conversion keeps multiple property values intact. + */ +api::ble_v2::GattCharacteristic::Property CPPGATTCharacteristicPropertyFromObjC( + CBCharacteristicProperties p); + +/** Converts an Objective-C characteristic to a C++ characteristic. */ +api::ble_v2::GattCharacteristic CPPGATTCharacteristicFromObjC(GNCBLEGATTCharacteristic *c); + +} // namespace apple +} // namespace nearby diff --git a/internal/platform/implementation/apple/ble_utils.mm b/internal/platform/implementation/apple/ble_utils.mm new file mode 100644 index 00000000..cf08649e --- /dev/null +++ b/internal/platform/implementation/apple/ble_utils.mm @@ -0,0 +1,159 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#import "internal/platform/implementation/apple/ble_utils.h" + +#import +#import + +#include + +#import "internal/platform/implementation/apple/Mediums/BLEv2/GNCBLEGATTCharacteristic.h" + +namespace nearby { +namespace apple { + +using GattCharacteristic = api::ble_v2::GattCharacteristic; +using Permission = api::ble_v2::GattCharacteristic::Permission; +using Property = api::ble_v2::GattCharacteristic::Property; +using WriteType = api::ble_v2::GattClient::WriteType; + +/** + * Checks if enum `a` contains enum `b` + * + * For example, `Contains(Permission::kRead | Permission::kWrite, Permission::kRead)` should return + * `YES`. + */ +template +BOOL Contains(T a, T b) { + return (a & b) == b; +} + +BOOL Contains(unsigned long a, unsigned long b) { return Contains(a, b); } + +CBUUID *CBUUID16FromCPP(const Uuid &uuid) { + return [CBUUID UUIDWithString:@(uuid.Get16BitAsString().c_str())]; +} + +CBUUID *CBUUID128FromCPP(const Uuid &uuid) { + return [CBUUID UUIDWithString:@(std::string(uuid).c_str())]; +} + +CBCharacteristicWriteType CBCharacteristicWriteTypeFromCPP(WriteType wt) { + switch (wt) { + case WriteType::kWithResponse: + return CBCharacteristicWriteWithResponse; + case WriteType::kWithoutResponse: + return CBCharacteristicWriteWithoutResponse; + } +} + +CBAttributePermissions CBAttributePermissionsFromCPP(Permission p) { + CBAttributePermissions permissions = 0; + if (Contains(p, Permission::kRead)) { + permissions |= CBAttributePermissionsReadable; + } + if (Contains(p, Permission::kWrite)) { + permissions |= CBAttributePermissionsWriteable; + } + return permissions; +} + +CBCharacteristicProperties CBCharacteristicPropertiesFromCPP(Property p) { + CBCharacteristicProperties properties = 0; + if (Contains(p, Property::kRead)) { + properties |= CBCharacteristicPropertyRead; + } + if (Contains(p, Property::kWrite)) { + properties |= CBCharacteristicPropertyWrite; + } + if (Contains(p, Property::kIndicate)) { + properties |= CBCharacteristicPropertyIndicate; + } + if (Contains(p, Property::kNotify)) { + properties |= CBCharacteristicPropertyNotify; + } + return properties; +} + +GNCBLEGATTCharacteristic *ObjCGATTCharacteristicFromCPP(const GattCharacteristic &c) { + CBUUID *characteristicUUID = CBUUID128FromCPP(c.uuid); + CBUUID *serviceUUID = CBUUID128FromCPP(c.service_uuid); + CBAttributePermissions permissions = CBAttributePermissionsFromCPP(c.permission); + CBCharacteristicProperties properties = CBCharacteristicPropertiesFromCPP(c.property); + return [[GNCBLEGATTCharacteristic alloc] initWithUUID:characteristicUUID + serviceUUID:serviceUUID + permissions:permissions + properties:properties]; +} + +Uuid CPPUUIDFromObjC(CBUUID *uuid) { + NSString *uuidString = uuid.UUIDString; + if (uuidString.length == 4) { + uuidString = [NSString stringWithFormat:@"0000%@-0000-1000-8000-00805F9B34FB", uuidString]; + } + NSArray *components = [uuidString componentsSeparatedByString:@"-"]; + NSCAssert(components.count == 5, @"Invalid UUID string: %@", uuidString); + uint64_t mostSignificantBits = strtoull(components[0].UTF8String, nil, 16); + mostSignificantBits <<= 16; + mostSignificantBits |= strtoull(components[1].UTF8String, nil, 16); + mostSignificantBits <<= 16; + mostSignificantBits |= strtoull(components[2].UTF8String, nil, 16); + + uint64_t leastSignificantBits = strtoull(components[3].UTF8String, nil, 16); + leastSignificantBits <<= 48; + leastSignificantBits |= strtoull(components[4].UTF8String, nil, 16); + + return Uuid(mostSignificantBits, leastSignificantBits); +} + +Permission CPPGATTCharacteristicPermissionFromObjC(CBAttributePermissions p) { + Permission permissions = Permission::kNone; + if (Contains(p, CBAttributePermissionsReadable)) { + permissions |= Permission::kRead; + } + if (Contains(p, CBAttributePermissionsWriteable)) { + permissions |= Permission::kWrite; + } + return permissions; +} + +Property CPPGATTCharacteristicPropertyFromObjC(CBCharacteristicProperties p) { + Property property = Property::kNone; + if (Contains(p, CBCharacteristicPropertyRead)) { + property |= Property::kRead; + } + if (Contains(p, CBCharacteristicPropertyWrite)) { + property |= Property::kWrite; + } + if (Contains(p, CBCharacteristicPropertyIndicate)) { + property |= Property::kIndicate; + } + if (Contains(p, CBCharacteristicPropertyNotify)) { + property |= Property::kNotify; + } + return property; +} + +GattCharacteristic CPPGATTCharacteristicFromObjC(GNCBLEGATTCharacteristic *c) { + GattCharacteristic characteristic; + characteristic.uuid = CPPUUIDFromObjC(c.characteristicUUID); + characteristic.service_uuid = CPPUUIDFromObjC(c.serviceUUID); + characteristic.permission = CPPGATTCharacteristicPermissionFromObjC(c.permissions); + characteristic.property = CPPGATTCharacteristicPropertyFromObjC(c.properties); + return characteristic; +} + +} // namespace apple +} // namespace nearby