diff options
Diffstat (limited to 'chromium/third_party/nearby/src/internal/platform/implementation/ios')
84 files changed, 7426 insertions, 0 deletions
diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/BUILD b/chromium/third_party/nearby/src/internal/platform/implementation/ios/BUILD new file mode 100644 index 00000000000..7c5c5be7ed7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/BUILD @@ -0,0 +1,72 @@ +# Copyright 2020 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. +load("//tools/build_defs/apple:ios.bzl", "ios_static_framework") + +licenses(["notice"]) + +package(default_visibility = ["//visibility:public"]) + +objc_library( + name = "Connections", + srcs = [ + "Source/Internal/GNCAdvertiser.mm", + "Source/Internal/GNCCore.mm", + "Source/Internal/GNCCoreConnection.mm", + "Source/Internal/GNCDiscoverer.mm", + "Source/Internal/GNCPayload.mm", + "Source/Internal/GNCPayloadListener.mm", + "Source/Internal/GNCUtils.mm", + "Source/Internal/platform.mm", + ], + hdrs = [ + "Source/GNCAdvertiser.h", + "Source/GNCConnection.h", + "Source/GNCConnections.h", + "Source/GNCDiscoverer.h", + "Source/GNCPayload.h", + "Source/Internal/GNCCore.h", + "Source/Internal/GNCCoreConnection.h", + "Source/Internal/GNCPayload+Internal.h", + "Source/Internal/GNCPayloadListener.h", + "Source/Internal/GNCUtils.h", + ], + deps = [ + "//connections:core", + "//connections:core_types", + "//internal/platform/implementation/ios/Source/Mediums", + "//internal/platform/implementation/ios/Source/Platform", + "//internal/platform/implementation/ios/Source/Shared", + "//third_party/objective_c/google_toolbox_for_mac:GTM_Logger", + "@com_google_absl//absl/functional:bind_front", + ], +) + +MIN_IOS_VERSION = "12.0" + +HDRS_POD = [ + "Source/GNCAdvertiser.h", + "Source/GNCConnection.h", + "Source/GNCDiscoverer.h", + "Source/GNCPayload.h", +] + +ios_static_framework( + name = "NearbyConnections_framework", + hdrs = HDRS_POD, + bundle_name = "NearbyConnections", + minimum_os_version = MIN_IOS_VERSION, + deps = [ + ":Connections", + ], +) diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.png b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.png Binary files differnew file mode 100644 index 00000000000..c495d68b156 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.png diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.xcodeproj/project.pbxproj b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.xcodeproj/project.pbxproj new file mode 100644 index 00000000000..5f57eaffa2d --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample.xcodeproj/project.pbxproj @@ -0,0 +1,355 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 41B7A2C3208F900100EBA53E /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2C2208F900100EBA53E /* AppDelegate.m */; }; + 41B7A2C6208F900100EBA53E /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2C5208F900100EBA53E /* ViewController.m */; }; + 41B7A2C9208F900100EBA53E /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2C7208F900100EBA53E /* Main.storyboard */; }; + 41B7A2CB208F900200EBA53E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2CA208F900200EBA53E /* Assets.xcassets */; }; + 41B7A2CE208F900200EBA53E /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */; }; + 41B7A2D1208F900200EBA53E /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 41B7A2D0208F900200EBA53E /* main.m */; }; +/* End PBXBuildFile section */ + +/* Begin PBXFileReference section */ + 41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = NearbyConnectionsExample.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 41B7A2C1208F900100EBA53E /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; }; + 41B7A2C2208F900100EBA53E /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; }; + 41B7A2C4208F900100EBA53E /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = "<group>"; }; + 41B7A2C5208F900100EBA53E /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = "<group>"; }; + 41B7A2C8208F900100EBA53E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; + 41B7A2CA208F900200EBA53E /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; }; + 41B7A2CD208F900200EBA53E /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; }; + 41B7A2CF208F900200EBA53E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 41B7A2D0208F900200EBA53E /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 41B7A2BB208F900100EBA53E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 41B7A2B5208F900000EBA53E = { + isa = PBXGroup; + children = ( + 41B7A2C0208F900100EBA53E /* NearbyConnectionsExample */, + 41B7A2BF208F900100EBA53E /* Products */, + ); + sourceTree = "<group>"; + }; + 41B7A2BF208F900100EBA53E /* Products */ = { + isa = PBXGroup; + children = ( + 41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */, + ); + name = Products; + sourceTree = "<group>"; + }; + 41B7A2C0208F900100EBA53E /* NearbyConnectionsExample */ = { + isa = PBXGroup; + children = ( + 41B7A2C1208F900100EBA53E /* AppDelegate.h */, + 41B7A2C2208F900100EBA53E /* AppDelegate.m */, + 41B7A2C4208F900100EBA53E /* ViewController.h */, + 41B7A2C5208F900100EBA53E /* ViewController.m */, + 41B7A2C7208F900100EBA53E /* Main.storyboard */, + 41B7A2CA208F900200EBA53E /* Assets.xcassets */, + 41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */, + 41B7A2CF208F900200EBA53E /* Info.plist */, + 41B7A2D0208F900200EBA53E /* main.m */, + ); + path = NearbyConnectionsExample; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 41B7A2BD208F900100EBA53E /* NearbyConnectionsExample */ = { + isa = PBXNativeTarget; + buildConfigurationList = 41B7A2D4208F900200EBA53E /* Build configuration list for PBXNativeTarget "NearbyConnectionsExample" */; + buildPhases = ( + 41B7A2BA208F900100EBA53E /* Sources */, + 41B7A2BB208F900100EBA53E /* Frameworks */, + 41B7A2BC208F900100EBA53E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = NearbyConnectionsExample; + productName = NearbyConnectionsExample; + productReference = 41B7A2BE208F900100EBA53E /* NearbyConnectionsExample.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 41B7A2B6208F900000EBA53E /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = Google; + TargetAttributes = { + 41B7A2BD208F900100EBA53E = { + CreatedOnToolsVersion = 9.3; + }; + }; + }; + buildConfigurationList = 41B7A2B9208F900000EBA53E /* Build configuration list for PBXProject "NearbyConnectionsExample" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 41B7A2B5208F900000EBA53E; + productRefGroup = 41B7A2BF208F900100EBA53E /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 41B7A2BD208F900100EBA53E /* NearbyConnectionsExample */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 41B7A2BC208F900100EBA53E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 41B7A2CE208F900200EBA53E /* LaunchScreen.storyboard in Resources */, + 41B7A2CB208F900200EBA53E /* Assets.xcassets in Resources */, + 41B7A2C9208F900100EBA53E /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 41B7A2BA208F900100EBA53E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 41B7A2C6208F900100EBA53E /* ViewController.m in Sources */, + 41B7A2D1208F900200EBA53E /* main.m in Sources */, + 41B7A2C3208F900100EBA53E /* AppDelegate.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 41B7A2C7208F900100EBA53E /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 41B7A2C8208F900100EBA53E /* Base */, + ); + name = Main.storyboard; + sourceTree = "<group>"; + }; + 41B7A2CC208F900200EBA53E /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 41B7A2CD208F900200EBA53E /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = "<group>"; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 41B7A2D2208F900200EBA53E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_BITCODE = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + }; + name = Debug; + }; + 41B7A2D3208F900200EBA53E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_ENABLE_OBJC_WEAK = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_BITCODE = NO; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.3; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 41B7A2D5208F900200EBA53E /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = NearbyConnectionsExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.google.NearbyConnectionsExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE = ""; + PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 41B7A2D6208F900200EBA53E /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_IDENTITY = "iPhone Developer"; + CODE_SIGN_STYLE = Automatic; + DEVELOPMENT_TEAM = ""; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)", + ); + INFOPLIST_FILE = NearbyConnectionsExample/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.google.NearbyConnectionsExample; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 41B7A2B9208F900000EBA53E /* Build configuration list for PBXProject "NearbyConnectionsExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 41B7A2D2208F900200EBA53E /* Debug */, + 41B7A2D3208F900200EBA53E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 41B7A2D4208F900200EBA53E /* Build configuration list for PBXNativeTarget "NearbyConnectionsExample" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 41B7A2D5208F900200EBA53E /* Debug */, + 41B7A2D6208F900200EBA53E /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 41B7A2B6208F900000EBA53E /* Project object */; +} diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.h new file mode 100644 index 00000000000..c1b1202cdf0 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.h @@ -0,0 +1,34 @@ +// +// Copyright (c) 2021 Google Inc. +// +// 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 +// +// http://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. +// +// AppDelegate.h +// NearbyConnectionsExample +// + +#import <UIKit/UIKit.h> + +/** + * A delegate for NSApplication to handle notifications about app launch and + * shutdown. Owned by the application object. + */ +@interface AppDelegate : UIResponder <UIApplicationDelegate> + +/** + * Main screen window displayed to the user which contains any active view + * hierarchy. + */ +@property(nonatomic) UIWindow *window; + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.m new file mode 100644 index 00000000000..69022507528 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/AppDelegate.m @@ -0,0 +1,23 @@ +// +// Copyright (c) 2021 Google Inc. +// +// 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 +// +// http://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. +// +// AppDelegate.m +// NearbyConnectionsExample +// + +#import "AppDelegate.h" + +@implementation AppDelegate +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000000..d8db8d65fd7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,98 @@ +{ + "images" : [ + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "20x20", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "29x29", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "40x40", + "scale" : "3x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "2x" + }, + { + "idiom" : "iphone", + "size" : "60x60", + "scale" : "3x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "20x20", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "29x29", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "40x40", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "1x" + }, + { + "idiom" : "ipad", + "size" : "76x76", + "scale" : "2x" + }, + { + "idiom" : "ipad", + "size" : "83.5x83.5", + "scale" : "2x" + }, + { + "idiom" : "ios-marketing", + "size" : "1024x1024", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/Contents.json b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +}
\ No newline at end of file diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/LaunchScreen.storyboard b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000000..f83f6fd5810 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="13122.16" systemVersion="17A277" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM"> + <dependencies> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13104.12"/> + <capability name="Safe area layout guides" minToolsVersion="9.0"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="EHf-IW-A2E"> + <objects> + <viewController id="01J-lp-oVM" sceneMemberID="viewController"> + <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> + <viewLayoutGuide key="safeArea" id="6Tk-OE-BBY"/> + </view> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="53" y="375"/> + </scene> + </scenes> +</document> diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/Main.storyboard b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/Main.storyboard new file mode 100644 index 00000000000..b39e95c8009 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Base.lproj/Main.storyboard @@ -0,0 +1,46 @@ +<?xml version="1.0" encoding="UTF-8"?> +<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14113" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="fos-Uz-R9B"> + <device id="retina4_7" orientation="portrait"> + <adaptation id="fullscreen"/> + </device> + <dependencies> + <deployment identifier="iOS"/> + <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14088"/> + <capability name="Safe area layout guides" minToolsVersion="9.0"/> + <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/> + </dependencies> + <scenes> + <!--View Controller--> + <scene sceneID="BVh-fg-s4x"> + <objects> + <viewController id="Tuj-3T-cB2" customClass="ViewController" sceneMemberID="viewController"> + <view key="view" contentMode="scaleToFill" id="9NC-2J-1xS"> + <rect key="frame" x="0.0" y="0.0" width="375" height="667"/> + <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> + <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/> + <viewLayoutGuide key="safeArea" id="wwJ-gb-kZn"/> + </view> + <navigationItem key="navigationItem" id="ega-4i-ebx"/> + </viewController> + <placeholder placeholderIdentifier="IBFirstResponder" id="xJQ-q8-wWg" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="433" y="-133"/> + </scene> + <!--Navigation Controller--> + <scene sceneID="Jcj-tp-czJ"> + <objects> + <navigationController id="fos-Uz-R9B" sceneMemberID="viewController"> + <navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="x6x-US-muI"> + <rect key="frame" x="0.0" y="20" width="375" height="44"/> + <autoresizingMask key="autoresizingMask"/> + </navigationBar> + <connections> + <segue destination="Tuj-3T-cB2" kind="relationship" relationship="rootViewController" id="Xa2-HD-mUn"/> + </connections> + </navigationController> + <placeholder placeholderIdentifier="IBFirstResponder" id="99I-fh-ObP" userLabel="First Responder" sceneMemberID="firstResponder"/> + </objects> + <point key="canvasLocation" x="-455" y="-132"/> + </scene> + </scenes> +</document> diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Info.plist b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Info.plist new file mode 100644 index 00000000000..44aa9d70a41 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/Info.plist @@ -0,0 +1,51 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>$(DEVELOPMENT_LANGUAGE)</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> + <key>LSRequiresIPhoneOS</key> + <true/> + <key>UILaunchStoryboardName</key> + <string>LaunchScreen</string> + <key>UIMainStoryboardFile</key> + <string>Main</string> + <key>UIRequiredDeviceCapabilities</key> + <array> + <string>armv7</string> + </array> + <key>UISupportedInterfaceOrientations</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>UISupportedInterfaceOrientations~ipad</key> + <array> + <string>UIInterfaceOrientationPortrait</string> + <string>UIInterfaceOrientationPortraitUpsideDown</string> + <string>UIInterfaceOrientationLandscapeLeft</string> + <string>UIInterfaceOrientationLandscapeRight</string> + </array> + <key>NSLocalNetworkUsageDescription</key> + <string>Exchange data with nearby devices running the NearbyConnectionsExmaple app.</string> + <key>NSBonjourServices</key> + <array> + <string>_54167B379724._tcp</string> + </array> +</dict> +</plist> diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.h new file mode 100644 index 00000000000..599609942ca --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.h @@ -0,0 +1,24 @@ +// +// Copyright (c) 2021 Google Inc. +// +// 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 +// +// http://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. +// +// ViewController.h +// NearbyConnectionsExample +// + +#import <UIKit/UIKit.h> + +/** View controller for demo by loading NearbyConnections lib for advertiser and discoverer. */ +@interface ViewController : UIViewController +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.m new file mode 100644 index 00000000000..f6c8b82aa52 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/ViewController.m @@ -0,0 +1,291 @@ +// +// Copyright (c) 2021 Google Inc. +// +// 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 +// +// http://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. +// +// ViewController.m +// NearbyConnectionsExample +// + +#import "ViewController.h" + +#import <NearbyConnections/NearbyConnections.h> + +NS_ASSUME_NONNULL_BEGIN + +static NSString *kServiceId = @"com.google.NearbyConnectionsExample"; +static NSString *kCellIdentifier = @"endpointCell"; + +// Simplified version of dispatch_after. +void delay(NSTimeInterval delay, dispatch_block_t block) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), + dispatch_get_main_queue(), block); +} + +// This class contains info about a discovered endpoint. +@interface EndpointInfo : NSObject +@property(nonatomic, readonly) id<GNCDiscoveredEndpointInfo> discInfo; +@property(nonatomic, nullable) id<GNCConnection> connection; +@end + +@implementation EndpointInfo +- (instancetype)initWithDiscoveredInfo:(id<GNCDiscoveredEndpointInfo>)discInfo { + self = [super init]; + if (self) { + _discInfo = discInfo; + } + return self; +} +@end + +@interface ViewController () <UITableViewDataSource, UITableViewDelegate> +@property(nonatomic) GNCAdvertiser *advertiser; +@property(nonatomic) GNCDiscoverer *discoverer; +@property(nonatomic) NSMutableDictionary<GNCEndpointId, EndpointInfo *> *endpoints; +@property(nonatomic, readonly) NSData *ping; +@property(nonatomic, readonly) NSData *pong; +@property(nonatomic) UITableView *tableView; +@property(nonatomic) UITextView *statusView; +@property(nonatomic) NSMutableDictionary<GNCEndpointId, id<GNCConnection> > *incomingConnections; +@end + +@implementation ViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + [self makeViews]; + + // Enable "info" log messages in the release build. + [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"GTMVerboseLogging"]; + + self.title = [[UIDevice currentDevice] name]; + NSData *endpointInfo = [self.title dataUsingEncoding:NSUTF8StringEncoding]; + _endpoints = [NSMutableDictionary dictionary]; + _ping = [@"ping" dataUsingEncoding:NSUTF8StringEncoding]; + _pong = [@"pong" dataUsingEncoding:NSUTF8StringEncoding]; + _incomingConnections = [NSMutableDictionary dictionary]; + + // The advertiser. + _advertiser = [GNCAdvertiser + advertiserWithEndpointInfo:endpointInfo + serviceId:kServiceId + strategy:GNCStrategyCluster + connectionInitiationHandler:^(GNCEndpointId endpointId, + id<GNCAdvertiserConnectionInfo> advConnInfo, + GNCConnectionResponseHandler responseHandler) { + // Show a status that a discoverer has requested a connection. + [self logStatus:@"Accepting connection request" final:NO]; + + // Accept the connection request. + responseHandler(GNCConnectionResponseAccept); + return [GNCConnectionResultHandlers + successHandler:^(id<GNCConnection> connection) { + // Save the connection until the ping-pong sequence is done. + self.incomingConnections[endpointId] = connection; + __block BOOL receivedPong = NO; + + // Send a ping, expecting the remote endpoint to send a pong. + [self logStatus:@"Connection established; sending ping" final:NO]; + [connection sendBytesPayload:[GNCBytesPayload payloadWithBytes:self.ping] + completion:^(GNCPayloadResult result) { + if (result == GNCPayloadResultSuccess) { + [self logStatus:@"Sent ping; waiting for pong" final:NO]; + + // Show an error if the pong isn't received in the expected + // timeframe. + delay(3.0, ^{ + if (receivedPong) { + [self logStatus:@"Error: Didn't receive pong" final:YES]; + [self.incomingConnections + removeObjectForKey:endpointId]; // close the connection + } + }); + } else { + [self logStatus:@"Error: Failed to send ping" final:YES]; + } + }]; + + // Return handlers for incoming payloads. + return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) { + handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) { + receivedPong = NO; + [self.incomingConnections removeObjectForKey:endpointId]; // close the connection + + // Show a status of whether the pong was received. + [self logStatus:[payload.bytes isEqual:self.pong] ? @"Received pong" + : @"Error: Payload is not pong" + final:YES]; + }; + }]; + } + failureHandler:^(GNCConnectionFailure result) { + [self + logStatus:(result == GNCConnectionFailureRejected) ? @"Error: Connection rejected" + : @"Error: Connection failed" + final:YES]; + }]; + }]; + + // The discoverer. + __weak typeof(self) weakSelf = self; + _discoverer = [GNCDiscoverer + discovererWithServiceId:kServiceId + strategy:GNCStrategyCluster + endpointFoundHandler:^(GNCEndpointId endpointId, + id<GNCDiscoveredEndpointInfo> endpointInfo) { + typeof(self) self = weakSelf; + + // An endpoint was discovered; add it to the endpoint list and UI. + self.endpoints[endpointId] = [[EndpointInfo alloc] initWithDiscoveredInfo:endpointInfo]; + [self.tableView reloadData]; + + // Return the lost handler for this endpoint. + return ^{ + typeof(self) self = weakSelf; // shadow + + // Endpoint disappeared; remove it from the endpoint list and UI. + [self.endpoints removeObjectForKey:endpointId]; + [self.tableView reloadData]; + }; + }]; +} + +#pragma mark - UITableViewDelegate + +- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { + // The user tapped on a cell; request a connection with it. + EndpointInfo *info = _endpoints[_endpoints.allKeys[indexPath.row]]; + if (!info) return; + + void (^deselectRow)(void) = ^{ + [tableView deselectRowAtIndexPath:indexPath animated:YES]; + }; + + [self logStatus:@"Requesting connection" final:NO]; + info.discInfo.requestConnection( + self.title, + ^(id<GNCDiscovererConnectionInfo> discConnInfo, + GNCConnectionResponseHandler responseHandler) { + // Accept the auth token. + [self logStatus:@"Accepting auth token" final:NO]; + responseHandler(GNCConnectionResponseAccept); + return ^(id<GNCConnection> connection) { + // Save the connection until the ping-pong sequence is done. + info.connection = connection; + __block BOOL receivedPing = NO; + + [self logStatus:@"Connection established; waiting for ping" final:NO]; + + // Show an error if the ping isn't received in the expected timeframe. + delay(3.0, ^{ + if (!receivedPing) { + deselectRow(); + [self logStatus:@"Error: Didn't receive ping" final:YES]; + info.connection = nil; // close the connection + } + }); + + // Return handlers for incoming payloads. + __weak id<GNCConnection> weakConnection = connection; // avoid a retain cycle + return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) { + handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) { + receivedPing = YES; + + // If a ping was received, send a pong back to the advertiser. + if ([payload.bytes isEqual:self.ping]) { + [self logStatus:@"Received ping; sending pong" final:NO]; + [weakConnection sendBytesPayload:[GNCBytesPayload payloadWithBytes:self.pong] + completion:^(GNCPayloadResult result) { + deselectRow(); + [self logStatus:(result == GNCPayloadResultSuccess) + ? @"Sent pong" + : @"Error: Failed to send pong" + final:YES]; + + // Pong was sent, so close the connection. + info.connection = nil; + }]; + } else { + deselectRow(); + [self logStatus:@"Error: Payload is not ping" final:YES]; + } + }; + }]; + }; + }, + ^(GNCConnectionFailure result) { + // Connection failed. + deselectRow(); + [self logStatus:(result == GNCConnectionFailureRejected) ? @"Connection rejected" + : @"Connection failed" + final:YES]; + }); +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:kCellIdentifier + forIndexPath:indexPath]; + cell.textLabel.text = _endpoints[_endpoints.allKeys[indexPath.row]].discInfo.name; + return cell; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [_endpoints.allKeys count]; +} + +#pragma mark - Private + +- (void)makeViews { + _tableView = [[UITableView alloc] initWithFrame:self.view.frame style:UITableViewStylePlain]; + [_tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:kCellIdentifier]; + _tableView.delegate = self; + _tableView.dataSource = self; + _tableView.rowHeight = 48; + _tableView.scrollEnabled = YES; + _tableView.showsVerticalScrollIndicator = YES; + _tableView.userInteractionEnabled = YES; + _tableView.bounces = YES; + _tableView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:_tableView]; + + // Make the status view. + UITextView * (^newTextView)(CGRect) = ^(CGRect frame) { + UITextView *textView = [[UITextView alloc] initWithFrame:frame]; + textView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleTopMargin; + textView.layer.borderColor = [UIColor blackColor].CGColor; + textView.layer.borderWidth = 1; + textView.editable = NO; + textView.textContainerInset = UIEdgeInsetsZero; + return textView; + }; + CGRect selfFrame = self.view.frame; + static const int kStatusHeight = 144; + CGRect statusFrame = (CGRect){{selfFrame.origin.x + 4, selfFrame.size.height - kStatusHeight}, + {selfFrame.size.width - 8, kStatusHeight - 4}}; + _statusView = newTextView(statusFrame); + [self.view addSubview:_statusView]; +} + +- (void)logStatus:(NSString *)status final:(BOOL)final { + _statusView.text = [NSString stringWithFormat:@"%@\n%@%@", _statusView.text, status, + final ? @"\n–––––––––––––––––––––––––" : @""]; + [_statusView scrollRangeToVisible:NSMakeRange(_statusView.text.length - 1, 1)]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/main.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/main.m new file mode 100644 index 00000000000..3111188ca69 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/NearbyConnectionsExample/main.m @@ -0,0 +1,27 @@ +// +// Copyright (c) 2021 Google Inc. +// +// 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 +// +// http://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. +// +// main.m +// NearbyConnectionsExample +// + +#import <UIKit/UIKit.h> +#import "AppDelegate.h" + +int main(int argc, char * argv[]) { + @autoreleasepool { + return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); + } +} diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/README.md b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/README.md new file mode 100755 index 00000000000..bf98df6070b --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/README.md @@ -0,0 +1,198 @@ +# Nearby Connections Sample App for iOS + +This is a sample app for third party developers using the Nearby Connections +library. On startup, it advertises and discovers. Discovered advertisers are +added to the list in the UI. When the user taps on an advertiser in the list, +the discoverer requests a connection with it. When the connection is +established, the advertiser sends a "ping" payload to the discoverer, which +sends "pong" payload back to the advertiser. The connection is then closed. + +## Setup + +1.Get the NearbyConnections_framework.zip from https://github.com/google/nearby/releases/tag/v0.0.1-ios, unzip it, +and put your unzipped folder under your project folder. The directory structure +looks like: + +``` +/NearbyConnectionsExample + /NearbyConnectionsExample + NearbyConnectionsExample.xcodeproj + README.md + /NearbyConnections.framework +``` + +2.Import NearbyConnections.framework + +- In Xcode, click the NearbyConnectionsExample in the left pane. And click the one in TARGETS-NearbyConnectionsExample at the left of right pane and the Build Phases at the right. +- See the **Link Binary With Libraries**, and press **+** to import the file - **libc++.tbd**. +- In Add **Other…**, import the NearbyConnections.framework folder which was unzipped. + +![Import framework in Xcode](./XcodeSetup.png) + +3.Update info.plist: + +- add **NSLocalNetworkUsageDescription** key with a description of your usage of Nearby Connections. +- add **NSBonjourServices** key. + for NSBonjourServices key, add the bonjour type name: `_54167B379724._tcp` + +> **54167B379724** is the 6 byte hash of service id **com.google.NearbyConnectionsExample** + +``` +// NearbyConnectionsExample/info.plist +... +<key>NSLocalNetworkUsageDescription</key> +<string>Exchange data with nearby devices running the NearbyConnectionsExmaple app.</string> +<key>NSBonjourServices</key> +<array> + <string>_54167B379724._tcp</string> +</array> +... +``` + + +4.Add service id and import `<NearbyConnections/NearbyConnections.h>` in your main view controller. + +``` +// NearbyConnectionsExample/ViewController.m + +static NSString *kServiceId = @"com.google.NearbyConnectionsExample"; + +#import <NearbyConnections/NearbyConnections.h> +``` + +## Code Snippets +Note: All of the callbacks in this library use blocks rather than delegates. Be careful to avoid retain cycles in your block implementations. See the [Apple documentation](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmPractical.html#//apple_ref/doc/uid/TP40004447-SW1) describing how to avoid retain cycles. + +Here is the skeleton code for an advertiser: + +```objc +_advertiser = [GNCAdvertiser + advertiserWithEndpointInfo:endpointInfo + serviceId:myServiceId + strategy:GNCStrategyCluster + connectionInitiationHandler:^(GNCEndpointId endpointId, + id<GNCAdvertiserConnectionInfo> connectionInfo, + GNCConnectionResponseHandler responseHandler) { + // Decide whether to accept or reject the connection. The following code would normally + // exist in the callback for an alert, for instance. + if (/* user rejected */) { + responseHandler(GNCConnectionResponseReject); // the user rejected the invitation + } else { + responseHandler(GNCConnectionResponseAccept); // the user accepted the invitation + + // Return connection result handlers, one of which is called depending on + // whether a successful connection was made. + return [GNCConnectionResultHandlers successHandler:^(id<GNCConnection> connection) { + // A successful connection was made. Save the connection somewhere, which can + // be used to send payloads to the remote endpoint. + + // Return the incoming payload and disconnect handlers. + return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) { + // Optionally set the Bytes payload handler. + handlers.bytesPayloadHandler = ^(GNCBytesPayload *payload) { + // Process the payload received from the remote endpoint. + }; + + // Optionally set the Stream payload handler. + handlers.streamPayloadHandler = ^(GNCStreamPayload *payload, NSProgress *progress) { + // Receipt of a Stream payload has started. Input can be read from the payload’s + // NSInputStream, and progress/cancellation is handled via the NSProgress object. + return ^(GNCPayloadResult result) { + if (result == GNCPayloadResultSuccess) { + // The payload has been successfully received. + } + }; + }; + + // Optionally set the disconnected handler. + handlers.disconnectedHandler = ^(GNCDisconnectedReason reason) { + // The connection was severed by either endpoint or lost. + }; + } + failureHandler:^(GNCConnectionFailure result) { + // Failed to make the connection. + }]); + } +}]; +``` + +Here is the skeleton code for a discoverer: + +```objc +_discoverer = + [GNCDiscoverer discovererWithServiceId:myServiceId + strategy:GNCStrategyCluster + endpointFoundHandler:^(GNCEndpointId endpointId, + id<GNCDiscoveredEndpointInfo> discEndpointInfo) { + // An endpoint was found. Typically you would add it to a list of nearby endpoints + // displayed in a UITableView, for instance. + + // The following code shows how to request a connection with the endpoint. This code + // would normally exist in the -didSelectRowAtIndexPath: of UITableViewDelegate. + if (/* user wants to request a connection */) { + requestHandler(myName, + // This block is called once an authentication string is generated between the endpoints. + ^(id<GNCDiscovererConnectionInfo> discConnInfo, GNCConnectionResponseHandler responseHandler) { + // Ask the user to confirm the authentication string. + UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Accept auth?" ...]; + [alert addAction:[UIAlertAction actionWithTitle:@"OK" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + responseHandler(GNCConnectionResponseAccept); + }]]; + [alert addAction:[UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + responseHandler(GNCConnectionResponseReject); + }]]; + [self presentViewController:alert animated:YES completion:^{}]; + + // Return a block that's called if the connection was successful. + return ^(id<GNCConnection> connection) { + // A successful connection was made. Save the connection somewhere, which can + // be used to send payloads to the remote endpoint. + + // Return incoming data handlers as in the advertiser example above. + return [GNCConnectionHandlers handlersWithBuilder:^(GNCConnectionHandlers *handlers) { + // Set up payload and disconnect handlers here as in the advertiser example above. + }]; + }; + }, + // This block is called if the connection failed for any reason. + ^(GNCConnectionFailure result) { + // Typically an alert would be shown here explaining why the connection failed. + }); + } + + // Return the endpoint-lost handler, which is called when the endpoint goes out of range + // or stops advertising. + return ^{ + // The endpoint disappeared. + }; +}]; +``` + +Here is an example of how to send a Bytes payload. The returned NSProgress object can be passed to UIProgressView to display a progress bar. + +```objc +NSProgress *progress = [connection + sendBytesPayload:[GNCBytesPayload payloadWithBytes:someData] + completion:^(GNCPayloadResult result) { + // Check status to see if it was successfully sent. + }]; +``` + +## Build and run + +![Sucessful running screenshot](./NearbyConnectionsExample.png) + +If you meet the following error in the debug panel of Xcode, you likely need to set up the keys listed in step 3 **Update info.plist**, as well as the service type. + +``` +1970-01-01 00:00:00.000 NearbyConnectionsExample[1383/0x16d87b000] [lvl=1] -[GNCMBonjourService netService:didNotPublish:] Error publishing: service: <NSNetService 0x283a18920> local _307BEAB11028._tcp. IjFQWEUwe-oAAA 51898, errorDic: { + NSNetServicesErrorCode = "-72008"; + NSNetServicesErrorDomain = 10; +} +``` +--- +NOTE: The iOS simulator is unstable when advertising. We recommend using an real iOS device. diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/XcodeSetup.png b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/XcodeSetup.png Binary files differnew file mode 100644 index 00000000000..4f353ffeaec --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Example/NearbyConnectionsExample/XcodeSetup.png diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCAdvertiser.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCAdvertiser.h new file mode 100644 index 00000000000..03272bb646d --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCAdvertiser.h @@ -0,0 +1,84 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This contains info about a discoverer endpoint intitiating a connection with an advertiser. */ +@protocol GNCAdvertiserConnectionInfo <NSObject> +/** This is a human readable name of the discoverer. */ +@property(nonatomic, readonly, copy) NSString *name; +/** This token can be used to verify the identity of the discoverer. */ +@property(nonatomic, readonly, copy) NSString *authToken; +@end + +/** This class contains success and failure handlers for the connection request. */ +@interface GNCConnectionResultHandlers : NSObject + +/** + * This factory method creates a pair of handlers for a successful or failed connection. + * + * @param successHandler This handler is called if both endpoints accept the connection. + * A @c GNCConnection object is passed, meaning that the connection has + * been established and you may start sending and receiving payloads. + * @param failureHandler This handler is called if either endpoint rejects the connection. + */ ++ (instancetype)successHandler:(GNCConnectionHandler)successHandler + failureHandler:(GNCConnectionFailureHandler)failureHandler; + +@end + +/** + * This handler is called when a discoverer requests a connection with an advertiser. In + * response, the advertiser should accept or reject via @c responseHandler. + * + * @param endpointId The ID of the endpoint. + * @param connectionInfo Information about the discoverer. + * @param responseHandler Handler for the connection response, which is either an acceptance or + * rejection of the connection request. + * @return Handlers for the final connection result. This will be called as soon as the final + * connection result is known, when either side rejects or both sides accept. + */ +typedef GNCConnectionResultHandlers *_Nonnull (^GNCAdvertiserConnectionInitiationHandler)( + GNCEndpointId endpointId, id<GNCAdvertiserConnectionInfo> connectionInfo, + GNCConnectionResponseHandler responseHandler); + +/** + * An advertiser broadcasts a service that can be seen by discoverers, which can then make + * requests to connect to it. Release the advertiser object to stop advertising. + */ +@interface GNCAdvertiser : NSObject + +/** + * Factory method that creates an advertiser. + * + * @param endpointInfo A data for endpoint info which contains readable name of this endpoint, + * to be displayed on other endpoints. + * @param serviceId A string that uniquely identifies the advertised service. + * @param strategy The connection topology to use. + * @param connectionInitiationHandler A handler that is called when a discoverer requests a + * connection with this endpoint. + */ ++ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo + serviceId:(NSString *)serviceId + strategy:(GNCStrategy)strategy + connectionInitiationHandler: + (GNCAdvertiserConnectionInitiationHandler)connectionInitiationHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnection.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnection.h new file mode 100644 index 00000000000..85efca47ce5 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnection.h @@ -0,0 +1,167 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +@class GNCBytesPayload, GNCStreamPayload, GNCFilePayload; + +/** Response to a connection request. */ +typedef NS_ENUM(NSInteger, GNCConnectionResponse) { + GNCConnectionResponseReject, // reject the connection request + GNCConnectionResponseAccept, // accept the connection request +}; + +/** Reason for a failed connection request. */ +typedef NS_ENUM(NSInteger, GNCConnectionFailure) { + GNCConnectionFailureRejected, // an endpoint rejected the connection request + GNCConnectionFailureUnknown, // there was an error while attempting to make the connection +}; + +/** Handler for a @c GNCConnectionFailure value. */ +typedef void (^GNCConnectionFailureHandler)(GNCConnectionFailure); + +/** Reasons that a connection can be severed by either endpoint. */ +typedef NS_ENUM(NSInteger, GNCDisconnectedReason) { + GNCDisconnectedReasonUnknown, // the endpoint can no longer be reached +}; + +/** Handler for a @c GNCDisconnectedReason value. */ +typedef void (^GNCDisconnectedHandler)(GNCDisconnectedReason); + +/** Result of a payload transfer. */ +typedef NS_ENUM(NSInteger, GNCPayloadResult) { + GNCPayloadResultSuccess, // Payload delivery was successful. + GNCPayloadResultFailure, // An error occurred during payload delivery. + GNCPayloadResultCanceled, // Payload delivery was canceled. +}; + +/** Handler for a @c GNCPayloadResult value. */ +typedef void (^GNCPayloadResultHandler)(GNCPayloadResult); + +/** Connection topology. See https://developers.google.com/nearby/connections/strategies. */ +typedef NS_ENUM(NSInteger, GNCStrategy) { + GNCStrategyCluster, // M-to-N + GNCStrategyStar, // 1-to-N + GNCStrategyPointToPoint, // 1-to-1 +}; + +/** Every endpoint has a unique identifier. */ +typedef NSString *GNCEndpointId; + +/** This handler receives a Bytes payload. It is called when the payload data is fully received. */ +typedef void (^GNCBytesPayloadHandler)(GNCBytesPayload *payload); + +/** + * This handler receives a Stream payload, signifying the start of receipt of a stream. The payload + * data should be read from the supplied input stream. The progress object can be used to monitor + * progress or cancel the operation. This handler must return a completion handler, which is + * called when the operation is finished. + */ +typedef GNCPayloadResultHandler _Nonnull (^GNCStreamPayloadHandler)(GNCStreamPayload *payload, + NSProgress *progress); + +/** + * This handler receives a File payload, signifying the start of receipt of a file. The + * progress object can be used to monitor progress or cancel the operation. This handler must + * return a completion handler, which is called when the operation finishes successfully or if + * there is an error. The file will be stored in a temporary location. If an error occurs or the + * operation is canceled, the file will contain all data that was received. It is the client's + * responsibility to delete the file when it is no longer needed. + */ +typedef GNCPayloadResultHandler _Nonnull (^GNCFilePayloadHandler)(GNCFilePayload *payload, + NSProgress *progress); + +/** This class contains optional handlers for a connection. */ +@interface GNCConnectionHandlers : NSObject + +/** + * This handler receives Bytes payloads. It is optional; apps that don't send and receive Bytes + * payloads need not supply this handler. + */ +@property(nonatomic, nullable) GNCBytesPayloadHandler bytesPayloadHandler; + +/** + * This handler receives a stream that delivers a payload in chunks. It is optional; apps that + * don't send and receive Stream payloads need not supply this handler. + */ +@property(nonatomic, nullable) GNCStreamPayloadHandler streamPayloadHandler; + +/** + * This handler receives a File payload. It is optional; apps that don't send and receive File + * payloads need not supply this handler. + * Note: File payloads are not yet supported. + */ +@property(nonatomic, nullable) GNCFilePayloadHandler filePayloadHandler; + +/** + * This handler is called when the connection is ended, whether due to the endpoint disconnecting + * or moving out of range. It is optional. + */ +@property(nonatomic, nullable) GNCDisconnectedHandler disconnectedHandler; + +/** + * This factory method lets you specify a subset of the connection handlers in a single expression. + * + * @param builderBlock Set up the handlers in this block. + */ ++ (instancetype)handlersWithBuilder:(void (^)(GNCConnectionHandlers *))builderBlock; + +@end + +/** + * This represents a connection with an endpoint. Use it to send payloads to the endpoint, and + * release it to disconnect. + */ +@protocol GNCConnection <NSObject> + +/** + * Send a Bytes payload. A progress object is returned, which can be used to monitor + * progress or cancel the operation. |completion| will be called when the operation completes + * (in all cases, even if failed or was canceled). + */ +- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload + completion:(GNCPayloadResultHandler)completion; + +/** + * Send a Stream payload. A progress object is returned, which can be used to monitor + * progress or cancel the operation. The stream data is read from the supplied NSInputStream. + * |completion| will be called when the operation completes. + */ +- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload + completion:(GNCPayloadResultHandler)completion; + +/** + * Send a File payload. A progress object is returned, which can be used to monitor progress or + * cancel the operation. |completion| will be called when the operation completes. + * Note: File payloads are not yet supported. + */ +- (NSProgress *)sendFilePayload:(GNCFilePayload *)payload + completion:(GNCPayloadResultHandler)completion; +@end + +/** + * This handler takes a @c GNCConnection object and returns a @c GNCConnectionHandlers + * object containing the desired payload and connection-ended handlers. + */ +typedef GNCConnectionHandlers *_Nonnull (^GNCConnectionHandler)(id<GNCConnection> connection); + +/** + * This handler takes a response to a connection request. Pass @c GNCConnectionResponseAccept to + * accept the request and @c GNCConnectionResponseReject to reject it. + */ +typedef void (^GNCConnectionResponseHandler)(GNCConnectionResponse response); + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnections.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnections.h new file mode 100644 index 00000000000..71c280b012c --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCConnections.h @@ -0,0 +1,20 @@ +// Copyright 2020 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. + +// Umbrella header file for Nearby Connections library. + +#import "GNCAdvertiser.h" +#import "GNCConnection.h" +#import "GNCDiscoverer.h" +#import "GNCPayload.h" diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCDiscoverer.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCDiscoverer.h new file mode 100644 index 00000000000..f9afe835a3f --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCDiscoverer.h @@ -0,0 +1,95 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This is info about an advertiser endpoint with which the discoverer has requested a connection. + */ +@protocol GNCDiscovererConnectionInfo <NSObject> +/** This token can be used to verify the identity of the advertiser. */ +@property(nonatomic, readonly, copy) NSString *authToken; +@end + +/** + * This handler is called to establish authorization with the advertiser. In response, + * @c responseHandler should be called to accept or reject the connection. + * + * @param connectionInfo Information about the advertiser. + * @param responseHandler Handler for the connection response, which is either an acceptance or + * rejection of the connection request. + * @return Handler for the connection if it was successful. + */ +typedef GNCConnectionHandler _Nonnull (^GNCDiscovererConnectionInitializationHandler)( + id<GNCDiscovererConnectionInfo> connectionInfo, GNCConnectionResponseHandler responseHandler); + +/** + * This handler should be called to request a connection with an advertiser. + * + * @param endpointInfo A data for endpoint info which contains readable name of this endpoint, + * to be displayed on other endpoints. + * @param authorizationHandler This handler is called to establish authorization. + * @param failureHandler This handler is called if there was an error making the connection. + */ +typedef void (^GNCConnectionRequester)( + NSData *endpointInfo, GNCDiscovererConnectionInitializationHandler connectionAuthorizationHandler, + GNCConnectionFailureHandler failureHandler); + +/** Information about an endpoint when it's discovered. */ +@protocol GNCDiscoveredEndpointInfo <NSObject> +/** The human readable name of the remote endpoint. */ +@property(nonatomic, readonly, copy) NSString *endpointName; +/** Information advertised by the remote endpoint. */ +@property(nonatomic, readonly, copy) NSData *endpointInfo; +/** Call this block to request a connection with the advertiser. */ +@property(nonatomic, readonly) GNCConnectionRequester requestConnection; +@end + +/** This handler is called when a previously discovered advertiser endpoint is lost. */ +typedef void (^GNCEndpointLostHandler)(void); + +/** + * This handler is called when an advertiser endpoint is discovered. + * + * @param endpointId The ID of the endpoint. + * @param connectionInfo Information about the endpoint. + * @return Block that is called when the endpoint is lost. + */ +typedef GNCEndpointLostHandler _Nonnull (^GNCEndpointFoundHandler)( + GNCEndpointId endpointId, id<GNCDiscoveredEndpointInfo> endpointInfo); + +/** + * A discoverer searches for endpoints advertising the specified service, and allows connection + * requests to be sent to them. Release the discoverer object to stop discovering. + */ +@interface GNCDiscoverer : NSObject + +/** + * Factory method that creates a discoverer. + * + * @param serviceId A string that uniquely identifies the advertised service to search for. + * @param strategy The connection topology to use. + * @param endpointFoundHandler This handler is called when an endpoint advertising the service is + * discovered. + */ ++ (instancetype)discovererWithServiceId:(NSString *)serviceId + strategy:(GNCStrategy)strategy + endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCPayload.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCPayload.h new file mode 100644 index 00000000000..83eef679275 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/GNCPayload.h @@ -0,0 +1,64 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** This class encapsulates a Bytes payload. */ +@interface GNCBytesPayload : NSObject + +/** The unique identifier of the payload. */ +@property(nonatomic, readonly) int64_t identifier; + +/** The content of the payload. */ +@property(nonatomic, readonly) NSData *bytes; + +/** + * Creates a Bytes payload object. + * Note: To maximize performance, @c bytes is strongly referenced, not copied. + */ ++ (instancetype)payloadWithBytes:(NSData *)bytes; + +@end + +/** This class encapsulates a Stream payload. */ +@interface GNCStreamPayload : NSObject + +/** The unique identifier of the payload. */ +@property(nonatomic, readonly) int64_t identifier; + +/** The payload data is read from this input stream. */ +@property(nonatomic, readonly) NSInputStream *stream; + ++ (instancetype)payloadWithStream:(NSInputStream *)stream; + +@end + +/** This class encapsulates a File payload. */ +@interface GNCFilePayload : NSObject + +/** The unique identifier of the payload. */ +@property(nonatomic, readonly) int64_t identifier; + +/** A URL that identifies the file. */ +@property(nonatomic, readonly, copy) NSURL *fileURL; + ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL; + ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier; + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCAdvertiser.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCAdvertiser.mm new file mode 100644 index 00000000000..b00bbebdbc3 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCAdvertiser.mm @@ -0,0 +1,317 @@ +// Copyright 2021 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/ios/Source/GNCAdvertiser.h" + +#include <string> + +#include "absl/functional/bind_front.h" +#include "connections/advertising_options.h" +#include "connections/core.h" +#include "connections/listeners.h" +#include "connections/params.h" +#include "connections/status.h" +#include "internal/platform/byte_array.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCUtils.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +using ::location::nearby::ByteArrayFromNSData; +using ::location::nearby::CppStringFromObjCString; +using ::location::nearby::ObjCStringFromCppString; +using ::location::nearby::connections::ConnectionListener; +using ::location::nearby::connections::AdvertisingOptions; +using ::location::nearby::connections::ConnectionRequestInfo; +using ::location::nearby::connections::ConnectionResponseInfo; +using ::location::nearby::connections::GNCStrategyToStrategy; +using ::location::nearby::connections::Medium; +using ResultListener = ::location::nearby::connections::ResultCallback; +using ::location::nearby::connections::Status; + +/** This is a GNCAdvertiserConnectionInfo that provides storage for its properties. */ +@interface GNCAdvertiserConnectionInfo : NSObject + +@property(nonatomic, readonly) NSString *name; +@property(nonatomic, readonly) NSString *authToken; + +- (instancetype)initWithName:(NSString *)name authToken:(NSString *)authToken; + +@end + +@implementation GNCAdvertiserConnectionInfo + +- (instancetype)initWithName:(NSString *)name authToken:(NSString *)authToken { + self = [super init]; + if (self) { + _name = [name copy]; + _authToken = [authToken copy]; + } + return self; +} + +@end + +/** Information retained about an endpoint before and after requesting a connection. */ +@interface GNCAdvertiserEndpointInfo : NSObject +@property(nonatomic) GNCAdvertiserConnectionInfo *connectionInfo; +@property(nonatomic) GNCConnectionResponse clientResponse; +@property(nonatomic) BOOL clientResponseReceived; // whether the client response has been received +@property(nonatomic, nullable) GNCConnectionResultHandlers *connectionResultHandlers; +@property(nonatomic, weak) GNCCoreConnection *connection; +@property(nonatomic) GNCConnectionHandlers *connectionHandlers; +@end + +@implementation GNCAdvertiserEndpointInfo + ++ (instancetype)infoWithEndpointConnectionInfo:(GNCAdvertiserConnectionInfo *)connInfo { + GNCAdvertiserEndpointInfo *info = [[GNCAdvertiserEndpointInfo alloc] init]; + info.connectionInfo = connInfo; + return info; +} + +@end + +/** GNCAdvertiser members. */ +@interface GNCAdvertiser () +@property(nonatomic) GNCCore *core; +@property(nonatomic) GNCAdvertiserConnectionInitiationHandler initiationHandler; +@property(nonatomic, assign) Status status; +@property(nonatomic) NSMutableDictionary<GNCEndpointId, GNCAdvertiserEndpointInfo *> *endpoints; +@end + +/** C++ classes passed to the core library by GNCAdvertiser. */ +namespace location { +namespace nearby { +namespace connections { + +/** This class contains the callbacks for establishing and severing a connection. */ +class GNCAdvertiserConnectionListener { + public: + explicit GNCAdvertiserConnectionListener(GNCAdvertiser *advertiser) : advertiser_(advertiser) {} + + void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) { + GNCAdvertiser *advertiser = advertiser_; // strongify + if (!advertiser) return; + + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId]; + if (endpointInfo) { + GTMLoggerError(@"Connection already initiated for endpoint: %@", endpointId); + } else { + // TODO(b/169292092): endpointInfo is an advertisement byte array. Need to implement to + // extract the endpoint name not just force to cast string. + NSString *name = ObjCStringFromCppString(std::string(info.remote_endpoint_info)); + NSString *authToken = ObjCStringFromCppString(info.authentication_token); + GNCAdvertiserConnectionInfo *connInfo = + [[GNCAdvertiserConnectionInfo alloc] initWithName:name authToken:authToken]; + endpointInfo = [GNCAdvertiserEndpointInfo infoWithEndpointConnectionInfo:connInfo]; + + // Call the connection initiation handler. Synchronous because it returns the connection + // result handlers. + dispatch_sync(dispatch_get_main_queue(), ^{ + __weak __typeof__(advertiser) weakAdvertiser = advertiser; + endpointInfo.connectionResultHandlers = advertiser.initiationHandler( + endpointId, (id<GNCAdvertiserConnectionInfo>)connInfo, + ^(GNCConnectionResponse response) { + __strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser; + endpointInfo.clientResponse = response; + endpointInfo.clientResponseReceived = YES; + if (response == GNCConnectionResponseAccept) { + // The connection was accepted by the client. + if (payload_listener_ == nullptr) { + payload_listener_ = std::make_unique<GNCPayloadListener>( + advertiser.core, + ^{ + return endpointInfo.connectionHandlers; + }, + ^{ + return endpointInfo.connection.payloads; + }); + } + advertiser.core->_core->AcceptConnection( + CppStringFromObjCString(endpointId), + PayloadListener{ + .payload_cb = absl::bind_front(&GNCPayloadListener::OnPayload, + payload_listener_.get()), + .payload_progress_cb = absl::bind_front( + &GNCPayloadListener::OnPayloadProgress, payload_listener_.get()), + }, + ResultListener{}); + } else { + // The connection was rejected by the client. + advertiser.core->_core->RejectConnection(CppStringFromObjCString(endpointId), + ResultListener{}); + } + }); + }); + advertiser.endpoints[endpointId] = endpointInfo; + } + } + + void OnAccepted(const std::string &endpoint_id) { + GNCAdvertiser *advertiser = advertiser_; // strongify + if (!advertiser) return; + + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId]; + if (!endpointInfo) { + GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId); + return; + } + + // The connection has been accepted by both endpoints, so create the GNCConnection object + // and pass it to |successHandler| for the client to use. It will be removed from |endpoints| + // when the client disconnects (on dealloc of GNCConnection). + // Note: Use a local strong reference to the connection object; don't just assign to + // |endpointInfo.connection|. Without a strong reference, the connection object can be + // deallocated before |successHandler| is called in the Release build. + __weak __typeof__(advertiser) weakAdvertiser = advertiser; + id<GNCConnection> connection = [GNCCoreConnection + connectionWithEndpointId:endpointId + core:advertiser.core + deallocHandler:^{ + __strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser; + if (!strongAdvertiser) return; + [strongAdvertiser.endpoints removeObjectForKey:endpointId]; + }]; + endpointInfo.connection = connection; + + // Callback is synchronous because it returns the connection handlers. + dispatch_sync(dispatch_get_main_queue(), ^{ + endpointInfo.connectionHandlers = + endpointInfo.connectionResultHandlers.successHandler(connection); + }); + } + + void OnRejected(const std::string &endpoint_id, Status status) { + GNCAdvertiser *advertiser = advertiser_; // strongify + if (!advertiser) return; + + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId]; + if (!endpointInfo) { + GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId); + return; + } + + // One side rejected, so call failureHandler with the connection status (we do this in all + // cases), and forget the endpoint. + dispatch_async(dispatch_get_main_queue(), ^{ + endpointInfo.connectionResultHandlers.failureHandler(GNCConnectionFailureRejected); + }); + [advertiser.endpoints removeObjectForKey:endpointId]; + } + + void OnDisconnected(const std::string &endpoint_id) { + GNCAdvertiser *advertiser = advertiser_; // strongify + if (!advertiser) return; + + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId]; + if (endpointInfo) { + if (endpointInfo.connection) { + GNCDisconnectedHandler disconnectedHandler = + endpointInfo.connectionHandlers.disconnectedHandler; + dispatch_async(dispatch_get_main_queue(), ^{ + if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown); + }); + } else { + GTMLoggerInfo(@"Disconnect for unconnected endpoint: %@", endpointId); + } + [advertiser.endpoints removeObjectForKey:endpointId]; + } else { + GTMLoggerInfo(@"Disconnect for unknown endpoint: %@", endpointId); + } + } + + void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) { + GNCAdvertiser *advertiser = advertiser_; // strongify + if (!advertiser) return; + + // TODO(b/169292092): Implement. + } + + private: + __weak GNCAdvertiser *advertiser_; + std::unique_ptr<GNCPayloadListener> payload_listener_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +using ::location::nearby::connections::GNCAdvertiserConnectionListener; + +@interface GNCAdvertiser () { + std::unique_ptr<GNCAdvertiserConnectionListener> advertiserListener; +}; + +@end + +@implementation GNCAdvertiser + ++ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo + serviceId:(NSString *)serviceId + strategy:(GNCStrategy)strategy + connectionInitiationHandler: + (GNCAdvertiserConnectionInitiationHandler)initiationHandler { + GNCAdvertiser *advertiser = [[GNCAdvertiser alloc] init]; + advertiser.initiationHandler = initiationHandler; + advertiser.endpoints = [[NSMutableDictionary alloc] init]; + advertiser.core = GNCGetCore(); + advertiser->advertiserListener = std::make_unique<GNCAdvertiserConnectionListener>(advertiser); + + ConnectionListener listener = { + .initiated_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnInitiated, + advertiser->advertiserListener.get()), + .accepted_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnAccepted, + advertiser->advertiserListener.get()), + .rejected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnRejected, + advertiser->advertiserListener.get()), + .disconnected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnDisconnected, + advertiser->advertiserListener.get()), + }; + + advertiser.core->_core->StartAdvertising( + CppStringFromObjCString(serviceId), + AdvertisingOptions{ + { + GNCStrategyToStrategy(strategy), // .strategy + location::nearby::connections::BooleanMediumSelector(), // .allowed + }, + true, // .auto_upgrade_bandwidth + true, // .enforce_topology_constraints + }, + ConnectionRequestInfo{ + .endpoint_info = ByteArrayFromNSData(endpointInfo), + .listener = std::move(listener), + }, + ResultListener{}); + return advertiser; +} + +- (void)dealloc { + GTMLoggerInfo(@"GNCAdvertiser deallocated"); + _core->_core->StopAdvertising(ResultListener{}); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.h new file mode 100644 index 00000000000..4c4f0e9e8b5 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.h @@ -0,0 +1,55 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#include <memory> + +#include "connections/core.h" +#include "connections/implementation/service_controller_router.h" +#include "internal/platform/payload_id.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This class contains the C++ Core object. */ +@interface GNCCore : NSObject { + @public + std::unique_ptr<::location::nearby::connections::Core> _core; + std::unique_ptr<::location::nearby::connections::ServiceControllerRouter> + _service_controller_router; +} + +/** + * These functions are the utilities to manipulate the InputFile in ImplementationPlatform for + * sending File payload. + * + * Inserts the URL to the map, keyed by payloadID. The element will not be inserted if there + * already is an element with the key in the map. + */ +- (void)insertURLToMapWithPayloadID:(::location::nearby::PayloadId)payloadId urlToSend:(NSURL *)url; + +/** + * Returns the URL with the payloadID and removes the entry from the map. Returns nil if + * payloadID is not found. + */ +- (nullable NSURL *)extractURLWithPayloadID:(::location::nearby::PayloadId)payloadId; + +- (void)clearSendingURLMaps; + +@end + +/** This function returns the Core singleton, wrapped in an Obj-C object for lifetime management. */ +GNCCore *GNCGetCore(); + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.mm new file mode 100644 index 00000000000..0abfc851e4a --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCore.mm @@ -0,0 +1,94 @@ +// Copyright 2020 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/ios/Source/Internal/GNCCore.h" + +#include <utility> + +#include "absl/container/flat_hash_map.h" +#include "absl/container/internal/common.h" +#include "connections/core.h" +#include "connections/implementation/service_controller_router.h" +#include "internal/platform/payload_id.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +using ::location::nearby::connections::Core; +using ::location::nearby::PayloadId; +using ::location::nearby::connections::ServiceControllerRouter; + +@implementation GNCCore { + // A map to store the NSURL object with PayloadId for sendFilePayload in GNCConnection. + // This is the place to store the NSURL for InputFile creation in ImplementationPlatform. + absl::flat_hash_map<PayloadId, NSURL *> _sending_urls; +} + +- (instancetype)init { + GTMLoggerInfo(@"GNCCore created"); + self = [super init]; + if (self) { + _service_controller_router = std::make_unique<ServiceControllerRouter>(); + _core = std::make_unique<Core>(_service_controller_router.get()); + } + return self; +} + +- (void)dealloc { + _core.reset(); + _service_controller_router.reset(); + GTMLoggerInfo(@"GNCCore deallocated"); +} + +- (void)insertURLToMapWithPayloadID:(PayloadId)payloadId urlToSend:(NSURL *)url { + _sending_urls.emplace(payloadId, url); +} + +- (nullable NSURL *)extractURLWithPayloadID:(PayloadId)payloadId { + NSURL *url; + auto it = _sending_urls.find(payloadId); + if (it != _sending_urls.end()) { + auto pair = _sending_urls.extract(it); + url = pair.mapped(); + } + return url; +} + +- (void)clearSendingURLMaps { + _sending_urls.clear(); +} + +@end + +GNCCore *GNCGetCore() { + static NSObject *syncSingleton; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + syncSingleton = [[NSObject alloc] init]; + }); + + // The purpose of keeping a weak reference to the GNCCore object is to ensure that it will be + // released when all external strong references are gone. I.e., when the app is no longer doing + // any NC operations, the core will be released. + static __weak GNCCore *core; + + // Strongly reference the GNCCore object for the duration of this function to ensure it isn't + // prematurely deallocated by ARC after being created (which can happen in optimized builds). + GNCCore *strongCore = core; + @synchronized(syncSingleton) { + if (!strongCore) { + strongCore = [[GNCCore alloc] init]; + core = strongCore; + } + } + return strongCore; +} diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h new file mode 100644 index 00000000000..08afddfd3ee --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h @@ -0,0 +1,44 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This holds the progress and completion for a pending payload. */ +@interface GNCPayloadInfo : NSObject +@property(nonatomic, nullable) NSProgress *progress; +@property(nonatomic, nullable) GNCPayloadResultHandler completion; + ++ (instancetype)infoWithProgress:(nullable NSProgress *)progress + completion:(GNCPayloadResultHandler)completion; +- (void)callCompletion:(GNCPayloadResult)result; +@end + +/** GNCConnection that interfaces with the Core library. */ +@interface GNCCoreConnection : NSObject <GNCConnection> +@property(nonatomic) GNCCore *core; +@property(nonatomic, copy) GNCEndpointId endpointId; +@property(nonatomic) dispatch_block_t deallocHandler; +@property(nonatomic) NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads; + ++ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId + core:(GNCCore *)core + deallocHandler:(dispatch_block_t)deallocHandler; +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.mm new file mode 100644 index 00000000000..a389763e679 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.mm @@ -0,0 +1,188 @@ +// Copyright 2020 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/ios/Source/Internal/GNCCoreConnection.h" + +#include "connections/core.h" +#include "connections/payload.h" +#include "internal/platform/exception.h" +#include "internal/platform/file.h" +#include "internal/platform/implementation/input_file.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/GNCPayload.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" +#include "internal/platform/input_stream.h" +#include "internal/platform/payload_id.h" + +using ::location::nearby::ByteArrayFromNSData; +using ::location::nearby::CppStringFromObjCString; +using ::location::nearby::InputFile; +using ::location::nearby::InputStream; +using ::location::nearby::connections::Payload; +using ::location::nearby::PayloadId; +using ResultListener = ::location::nearby::connections::ResultCallback; + +namespace location { +namespace nearby { +namespace connections { + +/** + * This InputStream subclass takes input from an NSInputStream. The update handler is called for + * each chunk of data sent, giving the client an opportunity to handle cancelation. + */ +class GNCInputStreamFromNSStream : public InputStream { + public: + explicit GNCInputStreamFromNSStream(NSInputStream *nsStream) : nsStream_(nsStream) { + [nsStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [nsStream open]; + } + + ~GNCInputStreamFromNSStream() override { Close(); } + + ExceptionOr<ByteArray> Read() { return Read(kMaxChunkSize); } + + ExceptionOr<ByteArray> Read(std::int64_t size) override { + uint8_t *bytesRead = new uint8_t[size]; + NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue]; + NSInteger numberOfBytesRead = [nsStream_ read:bytesRead maxLength:numberOfBytesToRead]; + if (numberOfBytesRead == 0) { + // Reached end of stream. + return ExceptionOr<ByteArray>(); + } else if (numberOfBytesRead < 0) { + // Stream error. + return ExceptionOr<ByteArray>{Exception::kIo}; + } + return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytesRead + length:numberOfBytesRead])); + } + + Exception Close() override { + [nsStream_ close]; + return {Exception::kSuccess}; + } + + private: + static const size_t kMaxChunkSize = 32 * 1024; + NSInputStream *nsStream_; + // dispatch_block_t update_handler_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +@implementation GNCPayloadInfo + ++ (instancetype)infoWithProgress:(nullable NSProgress *)progress + completion:(GNCPayloadResultHandler)completion { + GNCPayloadInfo *info = [[GNCPayloadInfo alloc] init]; + info.progress = progress; + info.completion = completion; + return info; +} + +- (void)callCompletion:(GNCPayloadResult)result { + if (_completion) _completion(result); + _completion = nil; +} + +@end + +@implementation GNCCoreConnection + ++ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId + core:(GNCCore *)core + deallocHandler:(dispatch_block_t)deallocHandler { + GNCCoreConnection *connection = [[GNCCoreConnection alloc] init]; + connection.endpointId = endpointId; + connection.core = core; + connection.deallocHandler = deallocHandler; + connection.payloads = [[NSMutableDictionary alloc] init]; + return connection; +} + +- (void)dealloc { + _core->_core->DisconnectFromEndpoint(CppStringFromObjCString(_endpointId), ResultListener{}); + _deallocHandler(); +} + +- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload + completion:(GNCPayloadResultHandler)completion { + Payload corePayload(ByteArrayFromNSData(payload.bytes)); + NSUInteger length = payload.bytes.length; + PayloadId payloadId = corePayload.GetId(); + NSProgress *progress = [NSProgress progressWithTotalUnitCount:length]; + __weak __typeof__(self) weakSelf = self; + progress.cancellationHandler = ^{ + [weakSelf cancelPayloadWithId:payloadId]; + }; + return [self sendPayload:std::move(corePayload) + size:length + progress:progress + completion:completion]; +} + +- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload + completion:(GNCPayloadResultHandler)completion { + NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1]; + + PayloadId payloadId = payload.identifier; + Payload corePayload(payloadId, [payload]() -> InputStream & { + location::nearby::connections::GNCInputStreamFromNSStream *stream = + new location::nearby::connections::GNCInputStreamFromNSStream(payload.stream); + return *stream; + }); + return [self sendPayload:std::move(corePayload) size:-1 progress:progress completion:completion]; +} + +- (NSProgress *)sendFilePayload:(GNCFilePayload *)payload + completion:(GNCPayloadResultHandler)completion { + NSProgress *progress = [NSProgress progressWithTotalUnitCount:0]; + + std::int64_t fileSize = 0; + NSURL *fileURL = payload.fileURL; + NSNumber *fileSizeValue = nil; + BOOL result = [fileURL getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil]; + if (result == YES) { + fileSize = fileSizeValue.longValue; + } + PayloadId payloadId = payload.identifier; + // Add the pair of payloadId and fileURL to the map in the GNCCore. + [_core insertURLToMapWithPayloadID:payloadId urlToSend:fileURL]; + Payload corePayload(payloadId, InputFile(payloadId, fileSize)); + progress.totalUnitCount = fileSize; + return [self sendPayload:std::move(corePayload) + size:fileSize + progress:progress + completion:completion]; +} + +#pragma mark Private + +- (NSProgress *)sendPayload:(Payload)payload + size:(uint64_t)size + progress:(NSProgress *)progress + completion:(GNCPayloadResultHandler)completion { + _payloads[@(payload.GetId())] = [GNCPayloadInfo infoWithProgress:progress completion:completion]; + _core->_core->SendPayload(std::vector<std::string>(1, CppStringFromObjCString(_endpointId)), + std::move(payload), ResultListener{}); + return progress; +} + +- (void)cancelPayloadWithId:(PayloadId)payloadId { + _core->_core->CancelPayload(payloadId, ResultListener{}); +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCDiscoverer.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCDiscoverer.mm new file mode 100644 index 00000000000..1175204bc6b --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCDiscoverer.mm @@ -0,0 +1,401 @@ +// Copyright 2021 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/ios/Source/GNCDiscoverer.h" + +#include <string> +#include <utility> + +#include "absl/functional/bind_front.h" +#include "connections/connection_options.h" +#include "connections/core.h" +#include "connections/discovery_options.h" +#include "connections/listeners.h" +#include "connections/status.h" +#include "internal/platform/byte_array.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCUtils.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +using ::location::nearby::ByteArray; +using ::location::nearby::CppStringFromObjCString; +using ::location::nearby::connections::DiscoveryOptions; +using ::location::nearby::connections::ConnectionOptions; +using ::location::nearby::connections::DiscoveryListener; +using ::location::nearby::connections::DistanceInfo; +using ::location::nearby::connections::GNCStrategyToStrategy; +using ResultListener = ::location::nearby::connections::ResultCallback; +using ::location::nearby::connections::Status; + +/** This is a GNCDiscovererConnectionInfo that provides storage for its properties. */ +@interface GNCDiscovererConnectionInfo : NSObject <GNCDiscovererConnectionInfo> +@property(nonatomic, copy) NSString *authToken; + +/** Creates a GNCDiscovererConnectionInfo object. */ ++ (instancetype)infoWithAuthToken:(NSString *)authToken; +@end + +@implementation GNCDiscovererConnectionInfo + ++ (instancetype)infoWithAuthToken:(NSString *)authToken { + GNCDiscovererConnectionInfo *info = [[GNCDiscovererConnectionInfo alloc] init]; + info.authToken = authToken; + return info; +} + +@end + +/** This is a GNCDiscoveredEndpointInfo that provides storage for its properties. */ +@interface GNCDiscoveredEndpointInfo : NSObject <GNCDiscoveredEndpointInfo> +@property(nonatomic, copy) NSString *endpointName; +@property(nonatomic, copy) NSData *endpointInfo; +@end + +@implementation GNCDiscoveredEndpointInfo + +@synthesize requestConnection = _requestConnection; + ++ (instancetype)infoWithName:(NSString *)endpointName + endpointInfo:(NSData *)endpointInfo + requestConnection:(GNCConnectionRequester)requestConnection { + GNCDiscoveredEndpointInfo *info = [[GNCDiscoveredEndpointInfo alloc] init]; + info.endpointName = endpointName; + info.endpointInfo = endpointInfo; + info->_requestConnection = requestConnection; + return info; +} + +@end + +/** Information retained by the discoverer about each discovered endpoint. */ +@interface GNCDiscovererEndpointInfo : NSObject + +/** Handles lostHandler once |onEndpointLost| has been callback. */ +@property(nonatomic) GNCEndpointLostHandler lostHandler; + +/** The connInitHandler is stored after requestConnection. */ +@property(nonatomic, nullable) GNCDiscovererConnectionInitializationHandler connInitHandler; + +/** The connFailureHandler is stored after requestConnection. */ +@property(nonatomic, nullable) GNCConnectionFailureHandler connFailureHandler; + +/** Client responses Accept or Reject. */ +@property(nonatomic) GNCConnectionResponse clientResponse; + +/** Whether the client response has been received. */ +@property(nonatomic) BOOL clientResponseReceived; + +/** + * The connectionhandler returned by connInitHandler. Stored here if the connection is accepted. + */ +@property(nonatomic, nullable) GNCConnectionHandler connectionHandler; + +/** @c GNCCoreConnection is created and stored if connection is accepted. */ +@property(nonatomic, weak) GNCCoreConnection *connection; + +/** @c GNCConnectionHandlers object is returned by connectionHandler and stored here. */ +@property(nonatomic) GNCConnectionHandlers *connectionHandlers; +@end + +@implementation GNCDiscovererEndpointInfo +@end + +/** GNCDiscoverer members. */ +@interface GNCDiscoverer () +@property(nonatomic) GNCCore *core; +@property(nonatomic) GNCEndpointFoundHandler endpointFoundHandler; +@property(nonatomic, assign) Status status; +@property(nonatomic) NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints; +@end + +/** C++ classes passed to the core library by GNCDiscoverer. */ +namespace location { +namespace nearby { +namespace connections { + +/** This class contains the discoverer callbacks related to a connection. */ +class GNCDiscovererConnectionListener { + public: + GNCDiscovererConnectionListener(GNCCore *core, + NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints) + : core_(core), endpoints_(endpoints) {} + + void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) { + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId]; + if (!endpointInfo) { + return; + } + + // Call the connection initiation handler. Synchronous because it returns the connection + // handler. + NSString *authToken = ObjCStringFromCppString(info.authentication_token); + GNCCore *core = core_; // don't capture |this| + dispatch_sync(dispatch_get_main_queue(), ^{ + endpointInfo.connectionHandler = endpointInfo.connInitHandler( + [GNCDiscovererConnectionInfo infoWithAuthToken:authToken], + ^(GNCConnectionResponse response) { + endpointInfo.clientResponse = response; + endpointInfo.clientResponseReceived = YES; + if (response == GNCConnectionResponseAccept) { + // The connect was accepted by the client. + if (payload_listener_ == nullptr) { + payload_listener_ = std::make_unique<GNCPayloadListener>( + core, + ^{ + return endpointInfo.connectionHandlers; + }, + ^{ + return endpointInfo.connection.payloads; + }); + } + core->_core->AcceptConnection( + CppStringFromObjCString(endpointId), + PayloadListener{ + .payload_cb = + absl::bind_front(&GNCPayloadListener::OnPayload, payload_listener_.get()), + .payload_progress_cb = absl::bind_front( + &GNCPayloadListener::OnPayloadProgress, payload_listener_.get()), + }, + ResultListener{}); + } else { + // The connect was rejected by the client. + core->_core->RejectConnection(CppStringFromObjCString(endpointId), ResultListener{}); + } + }); + }); + } + + void OnAccepted(const std::string &endpoint_id) { + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId]; + if (!endpointInfo) { + return; + } + + // The connection has been accepted by both endpoints, so create the GNCConnection object + // and pass it to |successHandler| for the client to use. + // Note: Use a local strong reference to the connection object; don't just assign to + // |endpointInfo.connection|. Without a strong reference, the connection object can be + // deallocated before |successHandler| is called in the Release build. + id<GNCConnection> connection = [GNCCoreConnection + connectionWithEndpointId:endpointId + core:core_ + deallocHandler:^{ + // Don't remove the remote endpoint (like GNCAdvertiser does) because that's + // done when the endpoint is lost. + }]; + endpointInfo.connection = connection; + + // Callback is synchronous because it returns the connection handlers. + dispatch_sync(dispatch_get_main_queue(), ^{ + endpointInfo.connectionHandlers = endpointInfo.connectionHandler(endpointInfo.connection); + }); + endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection + } + + void OnRejected(const std::string &endpoint_id, Status status) { + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId]; + if (!endpointInfo) { + return; + } + + // If either side rejected, call failureHandler with the connection status. + dispatch_async(dispatch_get_main_queue(), ^{ + endpointInfo.connFailureHandler(GNCConnectionFailureRejected); + }); + endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection + } + + void OnDisconnected(const std::string &endpoint_id) { + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId]; + if (!endpointInfo) { + return; + } + + if (endpointInfo.connection) { + GNCDisconnectedHandler disconnectedHandler = + endpointInfo.connectionHandlers.disconnectedHandler; + dispatch_async(dispatch_get_main_queue(), ^{ + if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown); + }); + } + } + + void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) { + // TODO(b/169292092): Implement. + } + + private: + GNCCore *core_; + NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints_; + std::unique_ptr<GNCPayloadListener> payload_listener_; +}; + +class GNCDiscoveryListener { + public: + explicit GNCDiscoveryListener(GNCDiscoverer *discoverer) : discoverer_(discoverer) {} + + void OnEndpointFound(const std::string &endpoint_id, const ByteArray &endpoint_info, + const std::string &service_id) { + GNCDiscoverer *discoverer = discoverer_; // strongify + if (!discoverer) { + return; + } + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + + if ([discoverer.endpoints objectForKey:endpointId] != nil) { + GTMLoggerError(@"Endpoint already discovered: %@", endpointId); + } else { + // The GNCDiscoveredEndpointInfo object created here lives as long as the client has strong + // reference to it. Here's the chain of strong references maintained here: + // client -> GNCDiscoveredEndpointInfo -> RequestConnection block -> + // GNCDiscovererEndpointInfo (stored weakly in the |endpoints| map table) + GNCDiscovererEndpointInfo *endpointInfo = [[GNCDiscovererEndpointInfo alloc] init]; + [discoverer.endpoints setObject:endpointInfo forKey:endpointId]; + + NSString *name = ObjCStringFromCppString(std::string(endpoint_info)); + NSData *info = NSDataFromByteArray(endpoint_info); + GNCCore *core = discoverer.core; // don't capture |this| or |discoverer| + NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints = discoverer.endpoints; + GNCDiscoveredEndpointInfo *discEndpointInfo = [GNCDiscoveredEndpointInfo + infoWithName:name + endpointInfo:info + requestConnection:^(NSData *info, + GNCDiscovererConnectionInitializationHandler connInitHandler, + GNCConnectionFailureHandler connFailureHandler) { + endpointInfo.connInitHandler = connInitHandler; + endpointInfo.connFailureHandler = connFailureHandler; + if (discoverer_connection_listener_ == nullptr) { + discoverer_connection_listener_ = + std::make_unique<GNCDiscovererConnectionListener>(core, endpoints); + } + + ConnectionListener listener = { + .initiated_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnInitiated, + discoverer_connection_listener_.get()), + .accepted_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnAccepted, + discoverer_connection_listener_.get()), + .rejected_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnRejected, + discoverer_connection_listener_.get()), + .disconnected_cb = + absl::bind_front(&GNCDiscovererConnectionListener::OnDisconnected, + discoverer_connection_listener_.get()), + }; + + core->_core->RequestConnection( + CppStringFromObjCString(endpointId), + ConnectionRequestInfo{.endpoint_info = ByteArrayFromNSData(info), + .listener = std::move(listener)}, + ConnectionOptions{}, + ResultListener{.result_cb = [&connFailureHandler](Status status) { + if (!status.Ok()) { + dispatch_sync(dispatch_get_main_queue(), ^{ + connFailureHandler(GNCConnectionFailureUnknown); + }); + } + }}); + }]; + + // Call the client endpoint-found handler. Tail call for reentrancy. + dispatch_sync(dispatch_get_main_queue(), ^{ + endpointInfo.lostHandler = discoverer.endpointFoundHandler(endpointId, discEndpointInfo); + }); + } + } + + void OnEndpointLost(const std::string &endpoint_id) { + GNCDiscoverer *discoverer = discoverer_; // strongify + if (!discoverer) { + return; + } + + NSString *endpointId = ObjCStringFromCppString(endpoint_id); + GNCDiscovererEndpointInfo *info = [discoverer.endpoints objectForKey:endpointId]; + if (!info) { + GTMLoggerError(@"Endpoint already lost: %@", endpointId); + } else { + dispatch_async(dispatch_get_main_queue(), ^{ + info.lostHandler(); + }); + } + [discoverer.endpoints removeObjectForKey:endpointId]; + } + + void OnEndpointDistanceChanged_cb(const std::string &endpoint_id, DistanceInfo info) { + // TODO(b/169292092): Implement. + } + + private: + __weak GNCDiscoverer *discoverer_; + std::unique_ptr<GNCDiscovererConnectionListener> discoverer_connection_listener_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +using ::location::nearby::connections::GNCDiscoveryListener; + +@interface GNCDiscoverer () { + std::unique_ptr<GNCDiscoveryListener> discoveryListener; +}; + +@end + +@implementation GNCDiscoverer + ++ (instancetype)discovererWithServiceId:(NSString *)serviceId + strategy:(GNCStrategy)strategy + endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler { + GNCDiscoverer *discoverer = [[GNCDiscoverer alloc] init]; + discoverer.endpointFoundHandler = endpointFoundHandler; + discoverer.endpoints = [NSMapTable strongToWeakObjectsMapTable]; + discoverer.core = GNCGetCore(); + discoverer->discoveryListener = std::make_unique<GNCDiscoveryListener>(discoverer); + + DiscoveryListener listener = { + .endpoint_found_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointFound, + discoverer->discoveryListener.get()), + .endpoint_lost_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointLost, + discoverer->discoveryListener.get()), + }; + + discoverer.core->_core->StartDiscovery(CppStringFromObjCString(serviceId), + DiscoveryOptions{ + { + GNCStrategyToStrategy(strategy), + }, + }, + std::move(listener), ResultListener{}); + + return discoverer; +} + +- (void)dealloc { + GTMLoggerInfo(@"GNCDiscoverer deallocated"); + _core->_core->StopDiscovery(ResultListener{}); +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload+Internal.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload+Internal.h new file mode 100644 index 00000000000..4792826c7e9 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload+Internal.h @@ -0,0 +1,36 @@ +// Copyright 2020 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/ios/Source/GNCPayload.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This category adds the ability to specify a payload ID. */ +@interface GNCBytesPayload (Internal) ++ (instancetype)payloadWithBytes:(NSData *)bytes identifier:(int64_t)identifier; +@end + + +/** This category adds the ability to specify a payload ID. */ +@interface GNCStreamPayload (Internal) ++ (instancetype)payloadWithStream:(NSInputStream *)stream identifier:(int64_t)identifier; +@end + + +/** This category adds the ability to specify a payload ID. */ +@interface GNCFilePayload (Internal) ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier; +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload.mm new file mode 100644 index 00000000000..30c88fe5e7a --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayload.mm @@ -0,0 +1,94 @@ +// Copyright 2020 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/ios/Source/GNCPayload.h" + +#include "connections/payload.h" +#include "internal/platform/payload_id.h" + +#include <stdlib.h> + +using ::location::nearby::connections::Payload; +using ::location::nearby::PayloadId; + +NS_ASSUME_NONNULL_BEGIN + +PayloadId GenerateId() { + return Payload::GenerateId(); +} + +@implementation GNCBytesPayload + +- (instancetype)initWithBytes:(NSData *)bytes identifier:(int64_t)identifier { + self = [super init]; + if (self) { + _identifier = identifier; + _bytes = bytes; + } + return self; +} + ++ (instancetype)payloadWithBytes:(NSData *)bytes { + return [[self alloc] initWithBytes:bytes identifier:GenerateId()]; +} + ++ (instancetype)payloadWithBytes:(NSData *)bytes identifier:(int64_t)identifier { + return [[self alloc] initWithBytes:bytes identifier:identifier]; +} + +@end + +@implementation GNCStreamPayload + +- (instancetype)initWithStream:(NSInputStream *)stream identifier:(int64_t)identifier { + self = [super init]; + if (self) { + _identifier = identifier; + _stream = stream; + } + return self; +} + ++ (instancetype)payloadWithStream:(NSInputStream *)stream { + return [[self alloc] initWithStream:stream identifier:GenerateId()]; +} + ++ (instancetype)payloadWithStream:(NSInputStream *)stream identifier:(int64_t)identifier { + return [[self alloc] initWithStream:stream identifier:identifier]; +} + +@end + +@implementation GNCFilePayload + +- (instancetype)initWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier { + self = [super init]; + if (self) { + _identifier = identifier; + _fileURL = [fileURL copy]; + } + return self; +} + ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL { + return [[self alloc] initWithFileURL:fileURL identifier:GenerateId()]; +} + ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier { + return [[self alloc] initWithFileURL:fileURL identifier:identifier]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h new file mode 100644 index 00000000000..51ae1a1c998 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.h @@ -0,0 +1,55 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" + +NS_ASSUME_NONNULL_BEGIN + +@class GNCPayloadInfo; + +namespace location { +namespace nearby { +namespace connections { + +/** This fetches a GNCConnectionHandlers object. */ +typedef GNCConnectionHandlers *_Nonnull (^GNCConnectionHandlersProvider)(); + +/** This fetches a payload dictionary. */ +typedef NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *_Nonnull (^GNCPayloadsProvider)(); + +/** This is the payload handler for an advertiser or discoverer. */ +class GNCPayloadListener : public PayloadListener { + public: + GNCPayloadListener(GNCCore *core, GNCConnectionHandlersProvider handlersProvider, + GNCPayloadsProvider payloadsProvider) + : core_(core), handlers_provider_(handlersProvider), payloads_provider_(payloadsProvider) {} + + void OnPayload(const std::string& endpoint_id, Payload payload); + void OnPayloadProgress(const std::string& endpoint_id, + const PayloadProgressInfo& info); + + private: + GNCCore *core_; + GNCConnectionHandlersProvider handlers_provider_; + GNCPayloadsProvider payloads_provider_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.mm new file mode 100644 index 00000000000..29c0feec54a --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCPayloadListener.mm @@ -0,0 +1,218 @@ +// Copyright 2020 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/ios/Source/Internal/GNCPayloadListener.h" + +#include <string> + +#include "connections/core.h" +#include "connections/listeners.h" +#include "connections/payload.h" +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/file.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" +#import "internal/platform/implementation/ios/Source/GNCPayload.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCCoreConnection.h" +#import "internal/platform/implementation/ios/Source/Internal/GNCPayload+Internal.h" +#include "internal/platform/implementation/ios/Source/Platform/utils.h" +#include "internal/platform/input_stream.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace location { +namespace nearby { +namespace connections { + +void GNCPayloadListener::OnPayload(const std::string &endpoint_id, Payload payload) { + GNCConnectionHandlers *handlers = handlers_provider_(); + int64_t payloadId = payload.GetId(); + + // Note: The payload must be destroyed by each individual payload type handler below, because in + // the Stream payload case, it runs an asynchronous read-write loop, which needs the payload + // and its stream to live until the stream ends. + NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_(); + + switch (payload.GetType()) { + case Payload::Type::kBytes: { + NSData *data = NSDataFromByteArray(payload.AsBytes()); // don't capture C++ object + + // Wait for the payload transfer update to arrive before calling the Bytes payload handler. + GNCPayloadInfo *info = [GNCPayloadInfo + infoWithProgress:nil + completion:^(GNCPayloadResult result) { + NSCAssert(result == GNCPayloadResultSuccess, @"Expected success"); + if (handlers.bytesPayloadHandler) { + // Call the Bytes payload handler. + dispatch_async(dispatch_get_main_queue(), ^{ + handlers.bytesPayloadHandler([GNCBytesPayload payloadWithBytes:data + identifier:payloadId]); + }); + } + }]; + payloads[@(payloadId)] = info; + break; + } + + case Payload::Type::kStream: + if (handlers.streamPayloadHandler) { + // Make a pair of bound streams so data pumped into the output stream becomes + // available for reading from the input stream. + NSInputStream *clientInputStream; + NSOutputStream *clientOutputStream; + // TODO(b/169292092): Base on medium's bandwidth? + [NSStream getBoundStreamsWithBufferSize:1024 + inputStream:&clientInputStream + outputStream:&clientOutputStream]; + + NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1]; // indeterminate + progress.cancellable = YES; + + // Pass the payload to the stream payload handler, receiving the completion handler from it. + // Since it returns a value, it must be called synchronously. + __block GNCPayloadResultHandler completion; + dispatch_sync(dispatch_get_main_queue(), ^{ + completion = handlers.streamPayloadHandler( + [GNCStreamPayload payloadWithStream:clientInputStream identifier:payloadId], + progress); + }); + GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion]; + payloads[@(payloadId)] = info; + + // This is a loop that reads data from the C++ input stream and writes it to the output + // stream that feeds it to the client input stream. + __block InputStream *payloadInputStream = payload.AsStream(); + dispatch_queue_t queue = + dispatch_queue_create("StreamReceiverQueue", DISPATCH_QUEUE_SERIAL); + dispatch_async(queue, ^{ + [clientOutputStream open]; + while (true) { + if (progress.isCancelled) { + // Payload was canceled by the client. + core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) { + // TODO(b/148640962): Implement. + }}); + break; + } + + ExceptionOr<ByteArray> readResult = payloadInputStream->Read(1024); + if (!readResult.ok()) { + // Error reading from stream. + // TODO(b/169292092): Tell core an error has occurred? + dispatch_async(dispatch_get_main_queue(), ^{ + [info callCompletion:GNCPayloadResultFailure]; + }); + break; + } + ByteArray byteArray = readResult.GetResult(); + if (byteArray.Empty()) { + // End of stream. + break; + } + + // Loop until it's all been consumed by the client output stream. + NSData *data = NSDataFromByteArray(byteArray); + NSUInteger totalLength = data.length; + NSUInteger totalNumberWritten = 0; + while (totalNumberWritten < totalLength) { + NSInteger numberWritten = + [clientOutputStream write:&((const uint8_t *)data.bytes)[totalNumberWritten] + maxLength:totalLength - totalNumberWritten]; + if (numberWritten <= 0) { // stream error or reached end of stream + // TODO(b/169292092): Tell core an error has occurred? + dispatch_async(dispatch_get_main_queue(), ^{ + [info callCompletion:GNCPayloadResultFailure]; + }); + break; + } + totalNumberWritten += numberWritten; + } + } + }); + } + break; + + case Payload::Type::kFile: + if (handlers.filePayloadHandler) { + InputFile *payloadInputFile = payload.AsFile(); + NSURL *fileURL = + [NSURL URLWithString:ObjCStringFromCppString(payloadInputFile->GetFilePath())]; + int64_t fileSize = payloadInputFile->GetTotalSize(); + NSProgress *progress = [NSProgress progressWithTotalUnitCount:fileSize]; + progress.cancellable = YES; + progress.cancellationHandler = ^{ + // Payload was canceled by the client. + core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) { + // TODO(b/148640962): Implement. + }}); + }; + + // Pass the payload to the file payload handler, receiving the completion handler from it. + // Since it returns a value, it must be called synchronously. + __block GNCPayloadResultHandler completion; + void (^passPayloadBlock)(void) = ^{ + completion = handlers.filePayloadHandler( + [GNCFilePayload payloadWithFileURL:fileURL identifier:payloadId], progress); + }; + if ([NSThread isMainThread]) { + passPayloadBlock(); + } else { + dispatch_sync(dispatch_get_main_queue(), passPayloadBlock); + } + GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion]; + payloads[@(payloadId)] = info; + } + break; + + default: + ;// fall through + } +} + +void GNCPayloadListener::OnPayloadProgress(const std::string &endpoint_id, + const PayloadProgressInfo &info) { + // Note: The logic in this callback for handling progress updates and payload completion is + // identical for Bytes, Stream and File payloads. + NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_(); + NSNumber *payloadId = @(info.payload_id); + GNCPayloadInfo *payloadInfo = payloads[payloadId]; + if (payloadInfo) { + // Update the progress. + if (payloadInfo.progress) { + payloadInfo.progress.completedUnitCount = info.bytes_transferred; + } + + // Call the completion handler for success/failure/canceled, but not in-progress. + if (info.status == PayloadProgressInfo::Status::kInProgress) { + return; + } + GNCPayloadResult result = + (info.status == PayloadProgressInfo::Status::kSuccess) ? GNCPayloadResultSuccess + : (info.status == PayloadProgressInfo::Status::kCanceled) ? GNCPayloadResultCanceled + : GNCPayloadResultFailure; + dispatch_async(dispatch_get_main_queue(), ^{ + payloadInfo.completion(result); + }); + + // Release the payload info. + [payloads removeObjectForKey:payloadId]; + } +} + +} // namespace connections +} // namespace nearby +} // namespace location + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.h new file mode 100644 index 00000000000..7f1e7b66289 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.h @@ -0,0 +1,42 @@ +// Copyright 2021 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 <Foundation/Foundation.h> + +#include <string> + +#include "connections/listeners.h" +#import "internal/platform/implementation/ios/Source/GNCAdvertiser.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace location { +namespace nearby { +namespace connections { + +/** Converts GNCStrategy to Strategy. */ +const Strategy& GNCStrategyToStrategy(GNCStrategy strategy); + +} // namespace connections +} // namespace nearby +} // namespace location + +/** Internal-only properties of the connection result handlers class. */ +@interface GNCConnectionResultHandlers () +@property(nonatomic) GNCConnectionHandler successHandler; +@property(nonatomic) GNCConnectionFailureHandler failureHandler; +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.mm new file mode 100644 index 00000000000..2f3aa81db29 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/GNCUtils.mm @@ -0,0 +1,77 @@ +// Copyright 2020 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/ios/Source/Internal/GNCUtils.h" + +#include "connections/strategy.h" +#import "internal/platform/implementation/ios/Source/GNCAdvertiser.h" +#import "internal/platform/implementation/ios/Source/GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace location { +namespace nearby { +namespace connections { + +const Strategy& GNCStrategyToStrategy(GNCStrategy strategy) { + switch (strategy) { + case GNCStrategyCluster: + return Strategy::kP2pCluster; + case GNCStrategyStar: + return Strategy::kP2pStar; + case GNCStrategyPointToPoint: + return Strategy::kP2pPointToPoint; + } +} + +} // namespace connections +} // namespace nearby +} // namespace location + +@implementation GNCConnectionHandlers + +- (instancetype)initWithBuilderBlock:(void (^)(GNCConnectionHandlers*))builderBlock { + self = [super init]; + if (self) { + builderBlock(self); + } + return self; +} + ++ (instancetype)handlersWithBuilder:(void (^)(GNCConnectionHandlers * _Nonnull))builderBlock { + return [[self alloc] initWithBuilderBlock:builderBlock]; +} + +@end + +@implementation GNCConnectionResultHandlers + +- (instancetype)initWithSuccessHandler:(GNCConnectionHandler)successHandler + failureHandler:(GNCConnectionFailureHandler)failureHandler { + self = [super init]; + if (self) { + _successHandler = successHandler; + _failureHandler = failureHandler; + } + return self; +} + ++ (instancetype)successHandler:(GNCConnectionHandler)successHandler + failureHandler:(GNCConnectionFailureHandler)failureHandler { + return [[self alloc] initWithSuccessHandler:successHandler failureHandler:failureHandler]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/platform.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/platform.mm new file mode 100644 index 00000000000..a7366bb97b4 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Internal/platform.mm @@ -0,0 +1,151 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/platform.h" + +#include <string> + +#import "internal/platform/implementation/ios/Source/Internal/GNCCore.h" +#include "internal/platform/implementation/ios/Source/Platform/atomic_boolean.h" +#include "internal/platform/implementation/ios/Source/Platform/atomic_uint32.h" +#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h" +#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h" +#include "internal/platform/implementation/ios/Source/Platform/input_file.h" +#import "internal/platform/implementation/ios/Source/Platform/log_message.h" +#import "internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h" +#include "internal/platform/implementation/ios/Source/Platform/mutex.h" +#import "internal/platform/implementation/ios/Source/Platform/scheduled_executor.h" +#import "internal/platform/implementation/ios/Source/Platform/single_thread_executor.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" +#include "internal/platform/implementation/ios/Source/Platform/wifi_lan.h" +#include "internal/platform/implementation/mutex.h" +#include "internal/platform/implementation/shared/file.h" +#include "internal/platform/payload_id.h" + +namespace location { +namespace nearby { +namespace api { + +namespace { +std::string GetPayloadPath(PayloadId payload_id) { + // This is to get a file path, e.g. /tmp/[payload_id], for the storage of payload file. + // NOTE: Per + // https://developer.apple.com/library/content/documentation/FileManagement/Conceptual/FileSystemProgrammingGuide/FileSystemOverview/FileSystemOverview.html + // Files saved in the /tmp directory will be deleted by the system. Callers should be responsible + // for copying the files to the permanent storage. + NSString* payloadIdString = ObjCStringFromCppString(std::to_string(payload_id)); + return CppStringFromObjCString( + [NSTemporaryDirectory() stringByAppendingPathComponent:payloadIdString]); +} +} // namespace + +// Atomics: +std::unique_ptr<AtomicBoolean> ImplementationPlatform::CreateAtomicBoolean(bool initial_value) { + return std::make_unique<ios::AtomicBoolean>(initial_value); +} + +std::unique_ptr<AtomicUint32> ImplementationPlatform::CreateAtomicUint32( + std::uint32_t initial_value) { + return std::make_unique<ios::AtomicUint32>(initial_value); +} + +std::unique_ptr<CountDownLatch> ImplementationPlatform::CreateCountDownLatch(std::int32_t count) { + return std::make_unique<ios::CountDownLatch>(count); +} + +std::unique_ptr<Mutex> ImplementationPlatform::CreateMutex(Mutex::Mode mode) { + // iOS does not support unchecked Mutex in debug mode, therefore + // ios::Mutex is used for both kRegular and kRegularNoCheck. + if (mode == Mutex::Mode::kRecursive) { + return absl::make_unique<ios::RecursiveMutex>(); + } else { + return absl::make_unique<ios::Mutex>(); + } +} + +std::unique_ptr<ConditionVariable> ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::make_unique<ios::ConditionVariable>(static_cast<ios::Mutex*>(mutex)); +} + +std::unique_ptr<InputFile> ImplementationPlatform::CreateInputFile(PayloadId payload_id, + std::int64_t total_size) { + // Extract the NSURL object with payload_id from |GNCCore| which stores the maps. If the retrieved + // NSURL object is not nil, we create InputFile by ios::InputFile. The difference is + // that ios::InputFile implements to read bytes from local real file for sending. + GNCCore* core = GNCGetCore(); + NSURL* url = [core extractURLWithPayloadID:payload_id]; + if (url != nil) { + return absl::make_unique<ios::InputFile>(url); + } else { + return shared::IOFile::CreateInputFile(GetPayloadPath(payload_id), total_size); + } +} + +std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(PayloadId payload_id) { + return shared::IOFile::CreateOutputFile(GetPayloadPath(payload_id)); +} + +std::unique_ptr<LogMessage> ImplementationPlatform::CreateLogMessage( + const char* file, int line, LogMessage::Severity severity) { + return absl::make_unique<ios::LogMessage>(file, line, severity); +} + +// Java-like Executors +std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateSingleThreadExecutor() { + return std::make_unique<ios::SingleThreadExecutor>(); +} + +std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateMultiThreadExecutor( + int max_concurrency) { + return std::make_unique<ios::MultiThreadExecutor>(max_concurrency); +} + +std::unique_ptr<ScheduledExecutor> ImplementationPlatform::CreateScheduledExecutor() { + return std::make_unique<ios::ScheduledExecutor>(); +} + +// Mediums +std::unique_ptr<BluetoothAdapter> ImplementationPlatform::CreateBluetoothAdapter() { + return nullptr; +} + +std::unique_ptr<BluetoothClassicMedium> ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr<ServerSyncMedium> ImplementationPlatform::CreateServerSyncMedium() { + return nullptr; +} + +std::unique_ptr<WifiMedium> ImplementationPlatform::CreateWifiMedium() { return nullptr; } + +std::unique_ptr<WifiLanMedium> ImplementationPlatform::CreateWifiLanMedium() { + return std::make_unique<ios::WifiLanMedium>(); +} + +std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() { return nullptr; } + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/BUILD b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/BUILD new file mode 100644 index 00000000000..65c811784c7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/BUILD @@ -0,0 +1,56 @@ +load("//tools/build_defs/apple:objc.bzl", "objc_proto_library") + +# Copyright 2020 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. +licenses(["notice"]) + +package(default_visibility = ["//internal/platform/implementation/ios:__subpackages__"]) + +objc_library( + name = "Mediums", + srcs = [ + "GNCLeaks.m", + "GNCMConnection.m", + "WifiLan/GNCMBonjourBrowser.m", + "WifiLan/GNCMBonjourConnection.m", + "WifiLan/GNCMBonjourService.m", + "WifiLan/GNCMBonjourUtils.m", + ], + hdrs = [ + "GNCLeaks.h", + "GNCMConnection.h", + "WifiLan/GNCMBonjourBrowser.h", + "WifiLan/GNCMBonjourConnection.h", + "WifiLan/GNCMBonjourService.h", + "WifiLan/GNCMBonjourUtils.h", + ], + deps = [ + ":ObjCProtos", + "//internal/platform/implementation/ios/Source/Shared", + "//third_party/objective_c/google_toolbox_for_mac:GTM_Logger", + "@com_google_absl//absl/numeric:int128", + ], +) + +objc_proto_library( + name = "ObjCProtos", + deps = [":Protos"], +) + +proto_library( + name = "Protos", + deps = [ + "//connections/implementation/proto:offline_wire_formats_proto", + ], +) diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.h new file mode 100644 index 00000000000..1b044fd3bd4 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.h @@ -0,0 +1,18 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +// Verifies that an object has been deallocated after the given time period. +void GNCVerifyDealloc(id object, NSTimeInterval timeInterval); diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.m new file mode 100644 index 00000000000..220326a9648 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCLeaks.m @@ -0,0 +1,27 @@ +// Copyright 2020 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/ios/Source/Mediums/GNCLeaks.h" + +void GNCVerifyDealloc(id object, NSTimeInterval timeInterval) { +#if DEBUG + __weak id weakObj = object; + NSCAssert(weakObj != nil, @"Pointer to %@ is already nil", weakObj); + NSLog(@"Verifying deallocation of %@", NSStringFromClass([weakObj class])); + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(timeInterval * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + NSCAssert(weakObj == nil, @"%@ not deallocated.", weakObj); + }); +#endif +} diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h new file mode 100644 index 00000000000..67d32fb1dd1 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h @@ -0,0 +1,101 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +/** Result of a medium payload transfer. */ +typedef NS_ENUM(NSInteger, GNCMPayloadResult) { + GNCMPayloadResultSuccess, // Payload delivery was successful. + GNCMPayloadResultFailure, // An error occurred during payload delivery. + GNCMPayloadResultCanceled, // Payload delivery was canceled. +}; + +/** Handler for a @c GNCMPayloadResult value. */ +typedef void (^GNCMPayloadResultHandler)(GNCMPayloadResult); + +/** + * A progress handler is periodically called during payload delivery. It is passed a value + * ranging from 0 (when the operation has just started) to the total size (when the operation is + * finished). + */ +typedef void (^GNCMProgressHandler)(size_t count); + +/** This handler is called when data is received from a remote endpoint. */ +typedef void (^GNCMPayloadHandler)(NSData *data); + +/** + * This represents a connection with a remote endpoint at the medium level. Use it to send + * payloads to the remote endpoint, and release it to disconnect. + */ +@protocol GNCMConnection <NSObject> + +/** + * Sends data to the remote endpoint. Wait for the completion to be called before sending another + * payload. + * + * @param payload The data to send. + * @param progressHandler Called repeatedly for progress feedback while the data is being sent. + * @param completion Callback called when the data has been fully sent, + * or it has failed to be sent (not connected or disconnected). + */ +- (void)sendData:(NSData *)payload + progressHandler:(GNCMProgressHandler)progressHandler + completion:(GNCMPayloadResultHandler)completion; + +@end + +/** This class contains optional handlers for a connection. */ +@interface GNCMConnectionHandlers : NSObject + +/** This handler is called when data is sent from the remote endpoint. */ +@property(nonatomic) GNCMPayloadHandler payloadHandler; + +/** This handler is called when the connection is ended. */ +@property(nonatomic) dispatch_block_t disconnectedHandler; + +/** This method creates a GNCMConnectionHandlers object from payload and disconnect handlers. */ ++ (instancetype)payloadHandler:(GNCMPayloadHandler)payloadHandler + disconnectedHandler:(dispatch_block_t)disconnectedHandler; + +@end + +/** + * This handler takes a GNCMConnection object and returns a GNCMConnectionHandlers object. It is + * called when a connection is successfully made with a remote endpoint. If |connection| is nil, + * the connection couldn't be established; in this case, return nil. + */ +typedef GNCMConnectionHandlers *_Nullable (^GNCMConnectionHandler)( + id<GNCMConnection> __nullable connection); + +/** + * This handler is called by a discovering endpoint to request a connection with an an advertising + * endpoint. + */ +typedef void (^GNCMConnectionRequester)(GNCMConnectionHandler connectionHandler); + +/** This handler is called when a previously discovered advertising endpoint is lost. */ +typedef void (^GNCMEndpointLostHandler)(void); + +/** + * This handler is called on a discoverer when a nearby advertising endpoint is + * discovered. Calls |requestConnection| to request a connection with the advertiser. + */ +typedef GNCMEndpointLostHandler _Nonnull (^GNCMEndpointFoundHandler)( + NSString *endpointId, NSString *serviceType, NSString *serviceName, + NSDictionary<NSString *, NSData *> *_Nullable TXTRecordData, + GNCMConnectionRequester requestConnection); + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.m new file mode 100644 index 00000000000..27b4c20fa62 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/GNCMConnection.m @@ -0,0 +1,31 @@ +// Copyright 2020 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/ios/Source/Mediums/GNCMConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation GNCMConnectionHandlers + ++ (instancetype)payloadHandler:(GNCMPayloadHandler)payloadHandler + disconnectedHandler:(dispatch_block_t)disconnectedHandler { + GNCMConnectionHandlers *handlers = [[GNCMConnectionHandlers alloc] init]; + handlers.payloadHandler = payloadHandler; + handlers.disconnectedHandler = disconnectedHandler; + return handlers; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h new file mode 100644 index 00000000000..6f5d338ee8f --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h @@ -0,0 +1,42 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GNCMBonjourBrowser browses mDNS services publishing the specified mDNS type and domain. The mDNS + * type is a string formatted as "_[serviceIdHash]._tcp." in which [serviceIdHash] is generated as + * a SHA-256 hash from the service ID and taken the 6 first bytes of string in upper case. + * Calls the specififed endpoint found handler when one is found. The endpoint found handler + * supplies a requester block, which can be called to establish a socket to the service found. + * + * Don't hold the strong reference of caller self in endpointFoundHandler to ensure there is no + * retain cycle between them. + * + * @param serviceType An mDNS type that uniquely identifies the published service to search for. + * @param endpointFoundHandler The handler that is called when an endpoint publishing the service + * ID is discovered. + */ +@interface GNCMBonjourBrowser : NSObject + +- (instancetype)initWithServiceType:(NSString *)serviceType + endpointFoundHandler:(GNCMEndpointFoundHandler)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m new file mode 100644 index 00000000000..d88f8a10929 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m @@ -0,0 +1,176 @@ +// Copyright 2020 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/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h" + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +typedef NSString *GNCEndpointId; + +@interface GNCMNetServiceInfo : NSObject +@property(nonatomic) NSNetService *service; +@property(nonatomic, copy) GNCMEndpointLostHandler endpointLostHandler; +@property(nonatomic, copy, nullable) GNCMConnectionHandler connectionHandler; +@end + +@implementation GNCMNetServiceInfo + ++ (instancetype)infoWithService:(NSNetService *)service { + GNCMNetServiceInfo *info = [[GNCMNetServiceInfo alloc] init]; + info.service = service; + return info; +} + +- (BOOL)isEqual:(GNCMNetServiceInfo *)object { + return [_service isEqual:object.service]; +} + +- (NSUInteger)hash { + return [_service hash]; +} + +@end + +@interface GNCMBonjourBrowser () <NSNetServiceBrowserDelegate, NSNetServiceDelegate> + +@property(nonatomic, copy) GNCMEndpointFoundHandler endpointFoundHandler; + +@property(nonatomic) NSNetServiceBrowser *netBrowser; +@property(nonatomic) NSMutableDictionary<GNCEndpointId, GNCMNetServiceInfo *> *endpoints; + +@end + +@implementation GNCMBonjourBrowser + +- (instancetype)initWithServiceType:(NSString *)serviceType + endpointFoundHandler:(GNCMEndpointFoundHandler)handler { + self = [super init]; + if (self) { + _endpointFoundHandler = handler; + + _netBrowser = [[NSNetServiceBrowser alloc] init]; + _netBrowser.delegate = self; + [_netBrowser scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [_netBrowser searchForServicesOfType:serviceType inDomain:GNCMBonjourDomain]; + _endpoints = [NSMutableDictionary dictionary]; + } + return self; +} + +#pragma mark NSNetServiceBrowserDelegate + +- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser { + GTMLoggerDebug(@"Browsing"); +} + +- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser { + GTMLoggerDebug(@"Stop browsing"); +} + +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser + didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict { + GTMLoggerDebug(@"Not browsing with errorDict: %@", errorDict); +} + +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser + didFindService:(NSNetService *)service + moreComing:(BOOL)moreComing { + GTMLoggerDebug(@"Found service: %@", service); + + GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service]; + + // Just to be safe, check if the service is already known and deal with it accordingly. + NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info]; + if (endpointIds.count > 0) return; + + // Add the newly discovered service to the list of services. + GNCEndpointId endpointId = [[NSUUID UUID] UUIDString]; + _endpoints[endpointId] = info; + + service.delegate = self; + [service resolveWithTimeout:0]; +} + +- (void)netServiceBrowser:(NSNetServiceBrowser *)browser + didRemoveService:(NSNetService *)service + moreComing:(BOOL)moreComing { + GTMLoggerDebug(@"Lost service: %@", service); + NSArray<GNCEndpointId> *endpointIds = + [_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]]; + NSAssert(endpointIds.count <= 1, @"Unexpected duplicate service"); + if (endpointIds.count > 0) { + GNCEndpointId endpointId = endpointIds[0]; + GNCMNetServiceInfo *info = _endpoints[endpointId]; + [_endpoints removeObjectForKey:endpointId]; + // Tail call to preserve reentrancy. + if (info.endpointLostHandler) { + info.endpointLostHandler(); + } + } +} + +#pragma mark NSNetServiceDelegate + +- (void)netServiceDidResolveAddress:(NSNetService *)service { + GTMLoggerDebug(@"Resolved service: %@ addresses: %@", service, service.addresses); + + GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service]; + + NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info]; + if (endpointIds.count > 0) { + // Get TXTRecord data. + NSData *data = [service TXTRecordData]; + NSDictionary<NSString *, NSData *> *TXTRecordData = + [NSNetService dictionaryFromTXTRecordData:data]; + + // The endpointLostHandler is returned from the endpointFoundHandler. The main benefit of this + // is that it allows rejection from the remote endpoint to be received by the local endpoint + // before the local endpoint has accepted or rejected. + info.endpointLostHandler = _endpointFoundHandler( + endpointIds[0], service.type, service.name, TXTRecordData, + ^(GNCMConnectionHandler connectionHandler) { + // A connection is requested, so resolve the service to get the I/O streams. + info.connectionHandler = connectionHandler; + if (info.connectionHandler) { + dispatch_sync(dispatch_get_main_queue(), ^{ + NSInputStream *inputStream; + NSOutputStream *outputStream; + [info.service scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [info.service getInputStream:&inputStream outputStream:&outputStream]; + GNCMBonjourConnection *connection = + [[GNCMBonjourConnection alloc] initWithInputStream:inputStream + outputStream:outputStream + queue:nil]; + connection.connectionHandlers = info.connectionHandler(connection); + }); + } + }); + _endpoints[endpointIds[0]] = info; + } +} + +- (void)netService:(NSNetService *)service + didNotResolve:(NSDictionary<NSString *, NSNumber *> *)errorDict { + GTMLoggerDebug(@"Did not resolve service: %@", service); + NSArray<GNCEndpointId> *endpointIds = + [_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]]; + if (endpointIds.count > 0) { + _endpoints[endpointIds[0]].connectionHandler = nil; + } +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h new file mode 100644 index 00000000000..e88ac9ee4ba --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h @@ -0,0 +1,38 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * This medium connection sends and receives payloads over the NSInputStream and NSOutputStream + * passed to it. + * + * @param inputStream The input stream to read from. + * @param outputStream The output stream to write to. + * @param queue The queue on which the GNCMConnection callbacks will be called. If nil, the main + * queue is used. + */ +@interface GNCMBonjourConnection : NSObject <GNCMConnection> +@property(nonatomic) GNCMConnectionHandlers *connectionHandlers; + +- (instancetype)initWithInputStream:(NSInputStream *)inputStream + outputStream:(NSOutputStream *)outputStream + queue:(nullable dispatch_queue_t)queue; +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m new file mode 100644 index 00000000000..ae318843348 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m @@ -0,0 +1,224 @@ +// Copyright 2020 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/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +enum { kMaxPacketSize = 32 * 1024 }; + +@interface GNCMBonjourConnection () <NSStreamDelegate> +@property(nonatomic) NSInputStream *inputStream; +@property(nonatomic) NSOutputStream *outputStream; +@property(nonatomic) dispatch_queue_t callbackQueue; + +@property(nonatomic, copy, nullable) NSData *dataBeingWritten; +@property(nonatomic) NSInteger numberOfBytesLeftToWrite; +@property(nonatomic, copy, nullable) GNCMProgressHandler progressHandler; +@property(nonatomic, copy, nullable) GNCMPayloadResultHandler completion; + +@property(nonatomic) BOOL inputStreamOpen; +@property(nonatomic) BOOL outputStreamOpen; +@end + +@implementation GNCMBonjourConnection + +- (instancetype)initWithInputStream:(NSInputStream *)inputStream + outputStream:(NSOutputStream *)outputStream + queue:(nullable dispatch_queue_t)queue { + self = [super init]; + if (self) { + _inputStreamOpen = NO; + _outputStreamOpen = NO; + + _inputStream = inputStream; + _inputStream.delegate = self; + [_inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [_inputStream open]; + + _outputStream = outputStream; + _outputStream.delegate = self; + [_outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [_outputStream open]; + + _callbackQueue = queue; + } + return self; +} + +- (void)dealloc { + [self closeStreams]; +} + +- (void)sendData:(NSData *)payload + progressHandler:(GNCMProgressHandler)progressHandler + completion:(GNCMPayloadResultHandler)completion { + if (_dataBeingWritten) { + GTMLoggerInfo(@"Attempting to send payload while one is already in flight"); + [self dispatchCallback:^{ + completion(GNCMPayloadResultFailure); + }]; + return; + } + + self.dataBeingWritten = payload; + self.numberOfBytesLeftToWrite = payload.length; + self.progressHandler = progressHandler; + self.completion = completion; + [self writeChunk]; +} + +#pragma mark NSStreamDelegate + +- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event { + switch (event) { + case NSStreamEventHasBytesAvailable: { + // Data has arrived on the input stream. + NSAssert(stream == _inputStream, @"Error: Expected input stream"); + + if (_connectionHandlers.payloadHandler) { + uint8_t bytesRead[kMaxPacketSize]; + NSInteger numberOfBytesRead; + @synchronized(self.inputStream) { + numberOfBytesRead = [self.inputStream read:bytesRead maxLength:kMaxPacketSize]; + } + NSData *data = nil; + if (numberOfBytesRead > 0) { + GTMLoggerInfo(@"Read %lu bytes", (u_long)numberOfBytesRead); + data = [NSData dataWithBytes:bytesRead length:numberOfBytesRead]; + } + [self dispatchCallback:^{ + self.connectionHandlers.payloadHandler(data ?: [NSData data]); + }]; + } + break; + } + + case NSStreamEventHasSpaceAvailable: + // There is space available on the output stream. + NSAssert(stream == _outputStream, @"Error: Expected output stream"); + + // Schedule this in a future runloop cycle because -writeChunk, which can cause this event + // to be received synchronously, is not reentrant. + [self performSelector:@selector(writeChunk) withObject:nil afterDelay:0.0]; + break; + + case NSStreamEventErrorOccurred: + GTMLoggerInfo(@"Stream error: %@", [stream streamError]); + // Fall through. + case NSStreamEventEndEncountered: { + GTMLoggerInfo(@"Stream closing"); + [self closeStreams]; + if (_connectionHandlers.disconnectedHandler) { + [self dispatchCallback:^{ + self.connectionHandlers.disconnectedHandler(); + }]; + } + break; + } + + case NSStreamEventOpenCompleted: + if (stream == _inputStream) { + _inputStreamOpen = YES; + } + if (stream == _outputStream) { + _outputStreamOpen = YES; + } + // Schedule this in a future runloop cycle because -writeChunk, which can cause this event + // to be received synchronously, is not reentrant. + [self performSelector:@selector(writeChunk) withObject:nil afterDelay:0.0]; + break; + + case NSStreamEventNone: + default: + break; + } +} + +#pragma mark Private + +// Calls a block on the callback queue. +- (void)dispatchCallback:(dispatch_block_t)block { + dispatch_async(_callbackQueue ?: dispatch_get_main_queue(), block); +} + +// Writes a chunk of the outgoing data to the output stream, calling the progress and completion +// handlers as needed. +- (void)writeChunk { + void (^reportProgress)(size_t) = ^(size_t count) { + // Captures the progress handler because the property is nilled out below. + GNCMProgressHandler progressHandler = _progressHandler; + if (progressHandler != nil) { + [self dispatchCallback:^{ + progressHandler(count); + }]; + } + }; + + void (^completed)(GNCMPayloadResult) = ^(GNCMPayloadResult result) { + reportProgress(_dataBeingWritten.length); + _progressHandler = nil; + _dataBeingWritten = nil; + + // Captures the completion because the property is nilled out below. + GNCMPayloadResultHandler completion = _completion; + if (completion != nil) { + [self dispatchCallback:^{ + completion(result); + }]; + } + _completion = nil; + }; + + @synchronized(_outputStream) { + if (_inputStreamOpen && _outputStreamOpen && _numberOfBytesLeftToWrite) { + NSUInteger dataLength = (UInt32)_dataBeingWritten.length; + if (_numberOfBytesLeftToWrite == dataLength) { + GTMLoggerInfo(@"Starting a write operation of length %lu", (u_long)dataLength); + } + NSInteger numberOfPayloadBytesWritten = + [_outputStream write:&_dataBeingWritten.bytes[dataLength - _numberOfBytesLeftToWrite] + maxLength:_numberOfBytesLeftToWrite]; + + GTMLoggerInfo(@"Wrote %lu bytes", (u_long)numberOfPayloadBytesWritten); + if (numberOfPayloadBytesWritten >= 0) { + _numberOfBytesLeftToWrite -= numberOfPayloadBytesWritten; + reportProgress(_dataBeingWritten.length - _numberOfBytesLeftToWrite); + if (_numberOfBytesLeftToWrite < 0) { + GTMLoggerInfo(@"Unexpected number of bytes written"); + _numberOfBytesLeftToWrite = 0; + } + if (_numberOfBytesLeftToWrite == 0) completed(GNCMPayloadResultSuccess); + } else { + GTMLoggerInfo(@"Error writing to output stream"); + completed(GNCMPayloadResultFailure); + } + } + } +} + +- (void)closeStreams { + @synchronized(_inputStream) { + [_inputStream close]; + _inputStream = nil; + } + + @synchronized(_outputStream) { + [_outputStream close]; + _outputStream = nil; + } +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.h new file mode 100644 index 00000000000..bdb31d26c86 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.h @@ -0,0 +1,48 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + * GNCMBonjourService publishes mDNS type and domain with the specified service ID via Apple + * Bonjour service. The mDNS type is a string formatted as "_[serviceIdHash]._tcp." in which + * [serviceIdHash] is generated as a SHA-256 hash from the service ID and taken the 6 first bytes + * of string in upper case. + * When the service connects, the specified |endpointConnectedHandler| is called, which establishes + * a connection to the browser. + * + * Don't hold the strong reference of caller self in endpointConnectedHandler to ensure there is no + * retain cycle between them. + * + * @param serviceName A service name that embeds the |WifiLanServiceInfo| information. + * @param serviceType An mDNS type that uniquely identifies the published service to search for. + * @param port The requesting socket port number. + * @param txtRecordData The TXTRecord data. + * @param endpointConnectedHandler The handler that is called when a browser connects. + */ +@interface GNCMBonjourService : NSObject + +- (instancetype)initWithServiceName:(NSString *)serviceName + serviceType:(NSString *)serviceType + port:(NSInteger)port + TXTRecordData:(NSDictionary<NSString *, NSData *> *)TXTRecordData + endpointConnectedHandler:(GNCMConnectionHandler)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.m new file mode 100644 index 00000000000..aa2176fd221 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.m @@ -0,0 +1,88 @@ +// Copyright 2020 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/ios/Source/Mediums/WifiLan/GNCMBonjourService.h" + +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +@interface GNCMBonjourService () <NSNetServiceDelegate> + +@property(nonatomic, copy) NSString *serviceName; +@property(nonatomic, copy) NSString *serviceType; +@property(nonatomic, copy) GNCMConnectionHandler endpointConnectedHandler; + +@property(nonatomic) NSNetService *netService; + +@end + +@implementation GNCMBonjourService + +- (instancetype)initWithServiceName:(NSString *)serviceName + serviceType:(NSString *)serviceType + port:(NSInteger)port + TXTRecordData:(NSDictionary<NSString *, NSData *> *)TXTRecordData + endpointConnectedHandler:(GNCMConnectionHandler)handler { + self = [super init]; + if (self) { + _serviceName = [serviceName copy]; + _serviceType = [serviceType copy]; + _endpointConnectedHandler = handler; + + _netService = [[NSNetService alloc] initWithDomain:GNCMBonjourDomain + type:_serviceType + name:_serviceName + port:port]; + [_netService setTXTRecordData:[NSNetService dataFromTXTRecordDictionary:TXTRecordData]]; + + _netService.delegate = self; + [_netService scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode]; + [_netService publishWithOptions:NSNetServiceListenForConnections]; + } + return self; +} + +- (void)dealloc { + [_netService stop]; +} + +#pragma mark NSNetServiceDelegate + +- (void)netServiceDidPublish:(NSNetService *)service { + GTMLoggerDebug(@"Did publish service: %@", service); +} + +- (void)netService:(NSNetService *)service + didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict { + GTMLoggerDebug(@"Error publishing: service: %@, errorDic: %@", service, errorDict); +} + +- (void)netServiceDidStop:(NSNetService *)service { + GTMLoggerDebug(@"Stopped publishing service: %@", service); +} + +- (void)netService:(NSNetService *)service + didAcceptConnectionWithInputStream:(NSInputStream *)inputStream + outputStream:(NSOutputStream *)outputStream { + GTMLoggerDebug(@"Accepted connection, service: %@", service); + GNCMBonjourConnection *connection = + [[GNCMBonjourConnection alloc] initWithInputStream:inputStream + outputStream:outputStream + queue:nil]; + connection.connectionHandlers = _endpointConnectedHandler(connection); +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h new file mode 100644 index 00000000000..e314dec6295 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h @@ -0,0 +1,18 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +// mDNS domain. +FOUNDATION_EXPORT NSString *_Nonnull const GNCMBonjourDomain; diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m new file mode 100644 index 00000000000..6f51015594d --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m @@ -0,0 +1,17 @@ +// Copyright 2020 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/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" + +NSString *const GNCMBonjourDomain = @"local"; diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/BUILD b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/BUILD new file mode 100644 index 00000000000..b739c1708d4 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/BUILD @@ -0,0 +1,94 @@ +# Copyright 2020 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. +licenses(["notice"]) + +package(default_visibility = ["//internal/platform/implementation/ios:__subpackages__"]) + +objc_library( + name = "Platform", + srcs = [ + "crypto.mm", + "input_file.mm", + "log_message.mm", + "multi_thread_executor.mm", + "scheduled_executor.mm", + "utils.mm", + "wifi_lan.mm", + ], + hdrs = [ + "input_file.h", + "log_message.h", + "multi_thread_executor.h", + "scheduled_executor.h", + "single_thread_executor.h", + "utils.h", + "wifi_lan.h", + ], + sdk_frameworks = [ + "CoreBluetooth", + "CoreFoundation", + ], + deps = [ + ":Platform_cc", + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "//internal/platform/implementation/ios/Source/Mediums", + "//internal/platform/implementation/ios/Source/Shared", + "//internal/platform/implementation/shared:file", + "//third_party/objective_c/google_toolbox_for_mac:GTM_Logger", + ], +) + +cc_library( + name = "Platform_cc", + srcs = [ + "condition_variable.cc", + "count_down_latch.cc", + "system_clock.cc", + ], + hdrs = [ + "atomic_boolean.h", + "atomic_uint32.h", + "condition_variable.h", + "count_down_latch.h", + "mutex.h", + ], + deps = [ + "//internal/platform/implementation:platform", + "//internal/platform/implementation:types", + "@com_google_absl//absl/strings:str_format", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + ], +) + +cc_test( + name = "Platform_cc_test", + srcs = [ + "atomic_boolean_test.cc", + "atomic_uint32_test.cc", + "condition_variable_test.cc", + "count_down_latch_test.cc", + "mutex_test.cc", + ], + shard_count = 16, + deps = [ + ":Platform_cc", + "@com_github_protobuf_matchers//protobuf-matchers", + "@com_google_absl//absl/synchronization", + "@com_google_absl//absl/time", + "@com_google_googletest//:gtest_main", + "@com_google_nisaba//nisaba/port:thread_pool/fiber", + ], +) diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean.h new file mode 100644 index 00000000000..ed3bbeebbf2 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean.h @@ -0,0 +1,46 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_ +#define PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_ + +#include <atomic> + +#include "internal/platform/implementation/atomic_boolean.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete AtomicBoolean implementation. +class AtomicBoolean : public api::AtomicBoolean { + public: + explicit AtomicBoolean(bool initial_value) : value_(initial_value) {} + ~AtomicBoolean() override = default; + + AtomicBoolean(const AtomicBoolean&) = delete; + AtomicBoolean& operator=(const AtomicBoolean&) = delete; + + bool Get() const override { return value_.load(); } + bool Set(bool value) override { return value_.exchange(value); } + + private: + std::atomic_bool value_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean_test.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean_test.cc new file mode 100644 index 00000000000..ed4c611db9d --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_boolean_test.cc @@ -0,0 +1,78 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/atomic_boolean.h" + +#include "gtest/gtest.h" +#include "thread/fiber/fiber.h" + +namespace location { +namespace nearby { +namespace ios { +namespace { + +TEST(AtomicBooleanTest, SetOnSameThread) { + AtomicBoolean atomic_boolean_{false}; + + EXPECT_EQ(false, atomic_boolean_.Get()); + + atomic_boolean_.Set(true); + EXPECT_EQ(true, atomic_boolean_.Get()); +} + +TEST(AtomicBooleanTest, MultipleSetGetOnSameThread) { + AtomicBoolean atomic_boolean_{false}; + + EXPECT_EQ(false, atomic_boolean_.Get()); + + atomic_boolean_.Set(true); + EXPECT_EQ(true, atomic_boolean_.Get()); + + atomic_boolean_.Set(true); + EXPECT_EQ(true, atomic_boolean_.Get()); + + atomic_boolean_.Set(false); + EXPECT_EQ(false, atomic_boolean_.Get()); + + atomic_boolean_.Set(true); + EXPECT_EQ(true, atomic_boolean_.Get()); +} + +TEST(AtomicBooleanTest, SetOnNewThread) { + AtomicBoolean atomic_boolean_{false}; + + EXPECT_EQ(false, atomic_boolean_.Get()); + + thread::Fiber f([&] { atomic_boolean_.Set(true); }); + f.Join(); + + EXPECT_EQ(true, atomic_boolean_.Get()); +} + +TEST(AtomicBooleanTest, GetOnNewThread) { + AtomicBoolean atomic_boolean_{false}; + + EXPECT_EQ(false, atomic_boolean_.Get()); + + atomic_boolean_.Set(true); + EXPECT_EQ(true, atomic_boolean_.Get()); + + thread::Fiber f([&] { EXPECT_EQ(true, atomic_boolean_.Get()); }); + f.Join(); +} + +} // namespace +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32.h new file mode 100644 index 00000000000..d9eb3acd487 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32.h @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_ +#define PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_ + +#include <atomic> +#include <cstdint> + +#include "internal/platform/implementation/atomic_reference.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete AtomicUint32 implementation. +class AtomicUint32 : public api::AtomicUint32 { + public: + explicit AtomicUint32(std::uint32_t initial_value) : value_(initial_value) {} + ~AtomicUint32() override = default; + + AtomicUint32(const AtomicUint32&) = delete; + AtomicUint32& operator=(const AtomicUint32&) = delete; + + std::uint32_t Get() const override { return value_; } + void Set(std::uint32_t value) override { value_ = value; } + + private: + std::atomic<std::uint32_t> value_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32_test.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32_test.cc new file mode 100644 index 00000000000..7893f3c2c1c --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/atomic_uint32_test.cc @@ -0,0 +1,64 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/atomic_uint32.h" + +#include "gtest/gtest.h" +#include "thread/fiber/fiber.h" + +namespace location { +namespace nearby { +namespace ios { +namespace { + +TEST(AtomicUint32Test, GetOnSameThread) { + std::uint32_t initial_value = 1450; + AtomicUint32 atomic_reference_{initial_value}; + + EXPECT_EQ(initial_value, atomic_reference_.Get()); +} + +TEST(AtomicUint32Test, SetGetOnSameThread) { + std::uint32_t initial_value_ = 1450; + AtomicUint32 atomic_reference_{initial_value_}; + + std::uint32_t new_value = 28; + atomic_reference_.Set(new_value); + EXPECT_EQ(new_value, atomic_reference_.Get()); +} + +TEST(AtomicUint32Test, SetOnNewThread) { + std::uint32_t initial_value_ = 1450; + AtomicUint32 atomic_reference_{initial_value_}; + + std::uint32_t new_thread_value = 28; + thread::Fiber f([&] { atomic_reference_.Set(new_thread_value); }); + f.Join(); + EXPECT_EQ(new_thread_value, atomic_reference_.Get()); +} + +TEST(AtomicUint32Test, GetOnNewThread) { + std::uint32_t initial_value_ = 1450; + AtomicUint32 atomic_reference_{initial_value_}; + + std::uint32_t new_value = 28; + atomic_reference_.Set(new_value); + thread::Fiber f([&] { EXPECT_EQ(new_value, atomic_reference_.Get()); }); + f.Join(); +} + +} // namespace +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.cc new file mode 100644 index 00000000000..8ad8752e0ac --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.cc @@ -0,0 +1,37 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h" + +#include "internal/platform/implementation/ios/Source/Platform/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +Exception ConditionVariable::Wait() { + condition_variable_.Wait(mutex_); + return {Exception::kSuccess}; +} + +Exception ConditionVariable::Wait(absl::Duration timeout) { + condition_variable_.WaitWithTimeout(mutex_, timeout); + return {Exception::kSuccess}; +} + +void ConditionVariable::Notify() { condition_variable_.SignalAll(); } + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.h new file mode 100644 index 00000000000..65486ff59b0 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable.h @@ -0,0 +1,48 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_ +#define PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_ + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/condition_variable.h" +#include "internal/platform/implementation/ios/Source/Platform/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete ConditionVariable implementation. +class ConditionVariable : public api::ConditionVariable { + public: + explicit ConditionVariable(ios::Mutex* mutex) : mutex_(&mutex->mutex_) {} + ~ConditionVariable() override = default; + + ConditionVariable(const ConditionVariable&) = delete; + ConditionVariable& operator=(const ConditionVariable&) = delete; + + Exception Wait() override; + Exception Wait(absl::Duration timeout) override; + void Notify() override; + + private: + absl::Mutex* mutex_; + absl::CondVar condition_variable_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_CONDITION_VARIABLE_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable_test.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable_test.cc new file mode 100644 index 00000000000..7d36f8641d7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/condition_variable_test.cc @@ -0,0 +1,84 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/condition_variable.h" + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "internal/platform/implementation/ios/Source/Platform/mutex.h" +#include "thread/fiber/fiber.h" + +namespace location { +namespace nearby { +namespace ios { +namespace { + +TEST(ConditionVariableTest, CanCreate) { + Mutex mutex{}; + ConditionVariable cond{&mutex}; +} + +TEST(ConditionVariableTest, CanWakeupWaiter) { + Mutex mutex{}; + ConditionVariable cond{&mutex}; + bool done = false; + bool waiting = false; + { + thread::Fiber f([&cond, &mutex, &done, &waiting] { + mutex.Lock(); + waiting = true; + cond.Wait(); + waiting = false; + done = true; + mutex.Unlock(); + }); + while (true) { + { + mutex.Lock(); + if (waiting) { + mutex.Unlock(); + break; + } + mutex.Unlock(); + } + absl::SleepFor(absl::Milliseconds(100)); + } + { + mutex.Lock(); + cond.Notify(); + EXPECT_FALSE(done); + mutex.Unlock(); + } + f.Join(); + } + EXPECT_TRUE(done); +} + +TEST(ConditionVariableTest, WaitTerminatesOnTimeoutWithoutNotify) { + Mutex mutex{}; + ConditionVariable cond{&mutex}; + mutex.Lock(); + + const absl::Duration kWaitTime = absl::Milliseconds(100); + absl::Time start = absl::Now(); + cond.Wait(kWaitTime); + absl::Duration duration = absl::Now() - start; + EXPECT_GE(duration, kWaitTime); + mutex.Unlock(); +} + +} // namespace +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.cc new file mode 100644 index 00000000000..9fce8728ba3 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.cc @@ -0,0 +1,40 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h" + +namespace location { +namespace nearby { +namespace ios { + +Exception CountDownLatch::Await() { + absl::MutexLock lock(&mutex_, absl::Condition(IsZeroOrNegative, &count_)); + return {Exception::kSuccess}; +} + +ExceptionOr<bool> CountDownLatch::Await(absl::Duration timeout) { + bool condition = mutex_.LockWhenWithTimeout( + absl::Condition(IsZeroOrNegative, &count_), timeout); + mutex_.Unlock(); + return ExceptionOr<bool>(condition); +} + +void CountDownLatch::CountDown() { + absl::MutexLock lock(&mutex_); + count_--; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.h new file mode 100644 index 00000000000..e5fcd6be20b --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch.h @@ -0,0 +1,49 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_ +#define PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_ + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/count_down_latch.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete CountDownLatch implementation. +class CountDownLatch : public api::CountDownLatch { + public: + explicit CountDownLatch(int count) : count_(count) {} + ~CountDownLatch() override = default; + + CountDownLatch(const CountDownLatch&) = delete; + CountDownLatch& operator=(const CountDownLatch&) = delete; + + Exception Await() override; + ExceptionOr<bool> Await(absl::Duration timeout) override; + void CountDown() override; + + private: + static bool IsZeroOrNegative(int* count) { return 0 >= *count; } + + absl::Mutex mutex_; + int count_ ABSL_GUARDED_BY(mutex_); +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch_test.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch_test.cc new file mode 100644 index 00000000000..2122bdedeb6 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/count_down_latch_test.cc @@ -0,0 +1,83 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/count_down_latch.h" + +#include "gtest/gtest.h" +#include "thread/fiber/fiber.h" + +namespace location { +namespace nearby { +namespace ios { +namespace { + +TEST(CountDownLatchTest, LatchAwaitCanWait) { + CountDownLatch latch(1); + std::atomic_bool done = false; + + thread::Fiber f([&done, &latch] { + done = true; + latch.CountDown(); + }); + f.Join(); + + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatchTest, LatchExtraCountDownIgnored) { + CountDownLatch latch(1); + std::atomic_bool done = false; + + thread::Fiber f([&done, &latch] { + done = true; + latch.CountDown(); + latch.CountDown(); + latch.CountDown(); + }); + f.Join(); + + latch.Await(); + EXPECT_TRUE(done); +} + +TEST(CountDownLatchTest, LatchAwaitWithTimeoutCanExpire) { + CountDownLatch latch(1); + + auto response = latch.Await(absl::Milliseconds(100)); + + EXPECT_TRUE(response.ok()); + EXPECT_FALSE(response.result()); +} + +TEST(CountDownLatchTest, InitialCountZero_AwaitDoesNotBlock) { + CountDownLatch latch(0); + + auto response = latch.Await(); + + EXPECT_TRUE(response.Ok()); +} + +TEST(CountDownLatchTest, InitialCountNegative_AwaitDoesNotBlock) { + CountDownLatch latch(-1); + + auto response = latch.Await(); + + EXPECT_TRUE(response.Ok()); +} + +} // namespace +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/crypto.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/crypto.mm new file mode 100644 index 00000000000..004cd56f561 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/crypto.mm @@ -0,0 +1,39 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/crypto.h" + +#import "absl/strings/string_view.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" +#import "internal/platform/implementation/ios/Source/Shared/GNCUtils.h" + +namespace location { +namespace nearby { + +void Crypto::Init() {} + +ByteArray Crypto::Md5(absl::string_view input) { + if (input.empty()) return ByteArray(); + + return ByteArrayFromNSData(GNCMd5String(ObjCStringFromCppString(input))); +} + +ByteArray Crypto::Sha256(absl::string_view input) { + if (input.empty()) return ByteArray(); + + return ByteArrayFromNSData(GNCSha256String(ObjCStringFromCppString(input))); +} + +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.h new file mode 100644 index 00000000000..6ccd59f78ef --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.h @@ -0,0 +1,48 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_INPUT_FILE_H_ +#define PLATFORM_IMPL_IOS_INPUT_FILE_H_ + +#import <Foundation/Foundation.h> + +#include "internal/platform/implementation/input_file.h" + +namespace location { +namespace nearby { +namespace ios { + +/** This InputFile subclass takes input from an NSURL. */ +class InputFile : public api::InputFile { + public: + explicit InputFile(NSURL *nsURL); + ~InputFile() override = default; + InputFile(InputFile &&) = default; + InputFile &operator=(InputFile &&) = default; + + ExceptionOr<ByteArray> Read(std::int64_t size) override; + std::string GetFilePath() const override; + std::int64_t GetTotalSize() const override; + Exception Close() override; + + private: + NSURL *nsURL_; + NSInputStream *nsStream_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_INPUT_FILE_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.mm new file mode 100644 index 00000000000..532f7e3461f --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/input_file.mm @@ -0,0 +1,69 @@ +// Copyright 2020 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/ios/Source/Platform/input_file.h" + +#include <string> + +#import "internal/platform/exception.h" +#import "internal/platform/implementation/ios/Source/Platform/utils.h" + +namespace location { +namespace nearby { +namespace ios { + +InputFile::InputFile(NSURL *nsURL) : nsURL_(nsURL) { + std::string string = CppStringFromObjCString([nsURL_ absoluteString]); + nsStream_ = [NSInputStream inputStreamWithURL:nsURL_]; + [nsStream_ scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode]; + [nsStream_ open]; +} + +ExceptionOr<ByteArray> InputFile::Read(std::int64_t size) { + uint8_t *bytes_read = new uint8_t[size]; + NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue]; + NSInteger numberOfBytesRead = [nsStream_ read:bytes_read maxLength:numberOfBytesToRead]; + if (numberOfBytesRead == 0) { + // Reached end of stream. + return ExceptionOr<ByteArray>(); + } else if (numberOfBytesRead < 0) { + // Stream error. + return ExceptionOr<ByteArray>(Exception::kIo); + } + return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytes_read + length:numberOfBytesRead])); +} + +std::string InputFile::GetFilePath() const { + return CppStringFromObjCString([nsURL_ absoluteString]); +} + +std::int64_t InputFile::GetTotalSize() const { + NSNumber *fileSizeValue = nil; + BOOL result = [nsURL_ getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil]; + if (result) { + return fileSizeValue.longValue; + } else { + return 0; + } +} + +Exception InputFile::Close() { + [nsStream_ close]; + return {Exception::kSuccess}; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.h new file mode 100644 index 00000000000..03e3dc1c593 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.h @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_LOG_MESSAGE_H_ +#define PLATFORM_IMPL_IOS_LOG_MESSAGE_H_ + +#include "glog/logging.h" +#include "internal/platform/implementation/log_message.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete LogMessage implementation +class LogMessage : public api::LogMessage { + public: + LogMessage(const char* file, int line, Severity severity); + ~LogMessage() override = default; + + LogMessage(const LogMessage&) = delete; + LogMessage& operator=(const LogMessage&) = delete; + + void Print(const char* format, ...) override; + + std::ostream& Stream() override; + + private: + google::LogMessage log_streamer_; + api::LogMessage::Severity severity_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // IPHONE_SHARED_NEARBY_CONNECTIONS_SOURCE_PLATFORM_LOG_MESSAGE_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.mm new file mode 100644 index 00000000000..c3772da2191 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/log_message.mm @@ -0,0 +1,87 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/log_message.h" + +#include "glog/logging.h" +#include "internal/platform/implementation/log_message.h" +#include "GoogleToolboxForMac/GTMLogger.h" + +namespace location { +namespace nearby { +namespace ios { + +api::LogMessage::Severity gMinLogSeverity = api::LogMessage::Severity::kInfo; + +GTMLoggerLevel ConvertSeverity(api::LogMessage::Severity severity) { + switch (severity) { + case api::LogMessage::Severity::kVerbose: + return kGTMLoggerLevelDebug; + case api::LogMessage::Severity::kInfo: + return kGTMLoggerLevelInfo; + case api::LogMessage::Severity::kWarning: + return kGTMLoggerLevelInfo; + case api::LogMessage::Severity::kError: + return kGTMLoggerLevelError; + case api::LogMessage::Severity::kFatal: + return kGTMLoggerLevelAssert; + } +} + +LogMessage::LogMessage(const char* file, int line, Severity severity) + : log_streamer_(ConvertSeverity(severity), file, line), severity_(severity) {} + +void LogMessage::Print(const char* format, ...) { + va_list ap; + va_start(ap, format); + switch (ConvertSeverity(severity_)) { + case kGTMLoggerLevelDebug: + [[GTMLogger sharedLogger] logDebug:[NSString stringWithUTF8String:format], ap]; + break; + case kGTMLoggerLevelInfo: + [[GTMLogger sharedLogger] logInfo:[NSString stringWithUTF8String:format], ap]; + break; + case kGTMLoggerLevelError: + [[GTMLogger sharedLogger] logError:[NSString stringWithUTF8String:format], ap]; + break; + case kGTMLoggerLevelAssert: + [[GTMLogger sharedLogger] logAssert:[NSString stringWithUTF8String:format], ap]; + break; + case kGTMLoggerLevelUnknown: + // no-op + break; + } +} + +// TODO(b/169292092): GTMLogger doesn't support stream. Temporarily use absl LogStreamer to make +// build pass. +std::ostream& LogMessage::Stream() { return log_streamer_.stream(); } + +} // namespace ios + +namespace api { + +// static +void LogMessage::SetMinLogSeverity(Severity severity) { ios::gMinLogSeverity = severity; } + +// static +bool LogMessage::ShouldCreateLogMessage(Severity severity) { + // TODO(b/169292092): GTMLogger doesn't support stream which cause crash. Temporarily turn off + // LogMessage. + return false; +} + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h new file mode 100644 index 00000000000..1a371580034 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h @@ -0,0 +1,47 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ +#define PLATFORM_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ + +#include "internal/platform/implementation/submittable_executor.h" +#import "internal/platform/runnable.h" +#import "internal/platform/implementation/ios/Source/Platform/scheduled_executor.h" + +namespace location { +namespace nearby { +namespace ios { + +class MultiThreadExecutor : public api::SubmittableExecutor { + public: + explicit MultiThreadExecutor(int max_concurrency); + ~MultiThreadExecutor() override = default; + + MultiThreadExecutor(const MultiThreadExecutor&) = delete; + MultiThreadExecutor& operator=(const MultiThreadExecutor&) = delete; + + // api::SubmittableExecutor: + void Shutdown() override; + void Execute(Runnable&& runnable) override; + bool DoSubmit(Runnable&& runnable) override; + + private: + std::unique_ptr<ScheduledExecutor> scheduled_executor_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.mm new file mode 100644 index 00000000000..e4a38712232 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/multi_thread_executor.mm @@ -0,0 +1,40 @@ +// Copyright 2020 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/ios/Source/Platform/multi_thread_executor.h" + +#import "internal/platform/implementation/ios/Source/Platform/scheduled_executor.h" +#include "internal/platform/runnable.h" + +namespace location { +namespace nearby { +namespace ios { + +MultiThreadExecutor::MultiThreadExecutor(int max_concurrency) { + scheduled_executor_ = std::make_unique<ScheduledExecutor>(max_concurrency); +} + +void MultiThreadExecutor::Shutdown() { scheduled_executor_->Shutdown(); } + +void MultiThreadExecutor::Execute(Runnable&& runnable) { + scheduled_executor_->Execute(std::move(runnable)); +} + +bool MultiThreadExecutor::DoSubmit(Runnable&& runnable) { + return scheduled_executor_->DoSubmit(std::move(runnable)); +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex.h new file mode 100644 index 00000000000..b60484d4cd0 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex.h @@ -0,0 +1,84 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_MUTEX_H_ +#define PLATFORM_IMPL_IOS_MUTEX_H_ + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/mutex.h" + +namespace location { +namespace nearby { +namespace ios { + +// Concrete Mutex implementation. +class ABSL_LOCKABLE Mutex : public api::Mutex { + public: + explicit Mutex() {} + ~Mutex() override = default; + + Mutex(const Mutex&) = delete; + Mutex& operator=(const Mutex&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + mutex_.Lock(); + mutex_.ForgetDeadlockInfo(); + } + void Unlock() ABSL_UNLOCK_FUNCTION() override { mutex_.Unlock(); } + + private: + friend class ConditionVariable; + absl::Mutex mutex_; +}; + +class ABSL_LOCKABLE RecursiveMutex : public api::Mutex { + public: + RecursiveMutex() = default; + ~RecursiveMutex() override = default; + + RecursiveMutex(RecursiveMutex&&) = delete; + RecursiveMutex& operator=(RecursiveMutex&&) = delete; + + void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() override { + intptr_t thread_id = ThreadId(); + if (thread_id_.load(std::memory_order_acquire) != thread_id) { + mutex_.Lock(); + thread_id_.store(thread_id, std::memory_order_release); + } + ++count_; + } + + void Unlock() ABSL_UNLOCK_FUNCTION() override { + if (--count_ == 0) { + thread_id_.store(0, std::memory_order_release); + mutex_.Unlock(); + } + } + + private: + static inline intptr_t ThreadId() { + ABSL_CONST_INIT thread_local int per_thread = 0; + return reinterpret_cast<intptr_t>(&per_thread); + } + + std::atomic<intptr_t> thread_id_{0}; + int count_{0}; + absl::Mutex mutex_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_MUTEX_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex_test.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex_test.cc new file mode 100644 index 00000000000..5c5a9dbe380 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/mutex_test.cc @@ -0,0 +1,92 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/ios/Source/Platform/mutex.h" + +#include "gtest/gtest.h" +#include "absl/synchronization/mutex.h" +#include "absl/synchronization/notification.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "thread/fiber/fiber.h" + +namespace location { +namespace nearby { +namespace ios { +namespace { + +static const absl::Duration kTimeToWait = absl::Milliseconds(500); + +TEST(MutexTest, LockOnce_UnlockOnce) { + Mutex test_mutex_1{}; + test_mutex_1.Lock(); + test_mutex_1.Unlock(); + + RecursiveMutex test_mutex_2; + test_mutex_2.Lock(); + test_mutex_2.Unlock(); +} + +TEST(MutexTest, BasicLockingWorks) { + absl::Notification lock_obtained; + Mutex test_mutex{}; + test_mutex.Lock(); + thread::Fiber f([&test_mutex, &lock_obtained] { + test_mutex.Lock(); + test_mutex.Unlock(); + lock_obtained.Notify(); + }); + EXPECT_FALSE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + test_mutex.Unlock(); + EXPECT_TRUE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + f.Join(); +} + +TEST(MutexTest, RecursiveLockingWorks) { + absl::Notification lock_obtained; + RecursiveMutex test_mutex; + test_mutex.Lock(); + thread::Fiber f([&test_mutex, &lock_obtained] { + test_mutex.Lock(); + test_mutex.Unlock(); + lock_obtained.Notify(); + }); + EXPECT_FALSE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + test_mutex.Unlock(); + EXPECT_TRUE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + f.Join(); +} + +TEST(MutexTest, RecursiveLockingForNestedWorks) { + absl::Notification lock_obtained; + RecursiveMutex test_mutex; + test_mutex.Lock(); + thread::Fiber f([&test_mutex, &lock_obtained]() + ABSL_NO_THREAD_SAFETY_ANALYSIS { + test_mutex.Lock(); + test_mutex.Lock(); + test_mutex.Unlock(); + test_mutex.Unlock(); + lock_obtained.Notify(); + }); + EXPECT_FALSE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + test_mutex.Unlock(); + EXPECT_TRUE(lock_obtained.WaitForNotificationWithTimeout(kTimeToWait)); + f.Join(); +} + +} // namespace +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.h new file mode 100644 index 00000000000..580389997c4 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.h @@ -0,0 +1,68 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_ +#define PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_ + +#import <Foundation/Foundation.h> + +#include <functional> +#include <memory> + +#include "internal/platform/implementation/scheduled_executor.h" +#include "internal/platform/runnable.h" + +/** + * The impl class is an Obj-C class so that + * (a) the dispatch block can strongly retain it, and + * (b) for ease of declaring an atomic property. + */ +@interface GNCOperationQueueImpl : NSObject +@property(nonatomic) NSOperationQueue* queue; +@property(atomic) BOOL shuttingDown; +@end + +namespace location { +namespace nearby { +namespace ios { + +// Concrete ScheduledExecutor implementation. +class ScheduledExecutor : public api::ScheduledExecutor { + public: + // The max_concurrency = 1 for default constructor. + ScheduledExecutor(); + explicit ScheduledExecutor(int max_concurrency); + ~ScheduledExecutor() override; + + ScheduledExecutor(const ScheduledExecutor&) = delete; + ScheduledExecutor& operator=(const ScheduledExecutor&) = delete; + + // api::ScheduledExecutor: + void Shutdown() override; + std::shared_ptr<api::Cancelable> Schedule(Runnable&& runnable, absl::Duration duration) override; + void Execute(Runnable&& runnable) override; + + bool DoSubmit(Runnable&& runnable); + + private: + void Shutdown(std::int64_t timeout_millis); + + GNCOperationQueueImpl* impl_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.mm new file mode 100644 index 00000000000..0df19ced163 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/scheduled_executor.mm @@ -0,0 +1,144 @@ +// Copyright 2020 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/ios/Source/Platform/scheduled_executor.h" + +#import <Foundation/Foundation.h> + +#include "absl/time/time.h" +#import "internal/platform/implementation/ios/Source/Platform/atomic_boolean.h" +#include "internal/platform/runnable.h" + +// This wraps the C++ Runnable in an Obj-C object for memory management. It is retained by the +// dispatch block below, and deleted when the block is released. +@interface GNCRunnableWrapper : NSObject { + @public + location::nearby::Runnable _runnable; + std::unique_ptr<location::nearby::ios::AtomicBoolean> _canceled; +} +@end + +@implementation GNCRunnableWrapper + ++ (instancetype)wrapperWithRunnable:(location::nearby::Runnable)runnable { + GNCRunnableWrapper *wrapper = [[GNCRunnableWrapper alloc] init]; + wrapper->_runnable = runnable; + wrapper->_canceled = std::make_unique<location::nearby::ios::AtomicBoolean>(false); + return wrapper; +} + +@end + +@implementation GNCOperationQueueImpl + ++ (instancetype)implWithMaxConcurrency:(int)maxConcurrency { + GNCOperationQueueImpl *impl = [[GNCOperationQueueImpl alloc] init]; + impl.queue = [[NSOperationQueue alloc] init]; + impl.queue.maxConcurrentOperationCount = maxConcurrency; + return impl; +} + +@end + +namespace location { +namespace nearby { +namespace ios { + +static const std::int64_t kExecutorShutdownDefaultTimeout = 500; // 0.5 seconds + +// This Cancelable references a Runnable and a cancel method that sets its canceled boolean to true. +class CancelableForRunnable : public api::Cancelable { + public: + explicit CancelableForRunnable(GNCRunnableWrapper *runnable) : runnable_(runnable) {} + CancelableForRunnable() = default; + ~CancelableForRunnable() override = default; + CancelableForRunnable(const CancelableForRunnable &) = delete; + CancelableForRunnable &operator=(const CancelableForRunnable &) = delete; + + // api::Cancelable: + bool Cancel() override { + runnable_->_canceled->Set(true); + return true; + } + + private: + GNCRunnableWrapper *runnable_; +}; + +ScheduledExecutor::ScheduledExecutor() { impl_ = [GNCOperationQueueImpl implWithMaxConcurrency:1]; } + +ScheduledExecutor::ScheduledExecutor(int max_concurrency) { + impl_ = [GNCOperationQueueImpl implWithMaxConcurrency:max_concurrency]; +} + +ScheduledExecutor::~ScheduledExecutor() { impl_ = nil; } + +void ScheduledExecutor::Shutdown() { Shutdown(kExecutorShutdownDefaultTimeout); } + +std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(Runnable &&runnable, + absl::Duration duration) { + if (impl_.shuttingDown) return std::shared_ptr<api::Cancelable>(nullptr); + + // Wrap the runnable in an Obj-C object so it can be referenced by the delayed block. + GNCRunnableWrapper *wrapper = [GNCRunnableWrapper wrapperWithRunnable:std::move(runnable)]; + CancelableForRunnable *cancelable = new CancelableForRunnable(wrapper); + GNCOperationQueueImpl *impl = impl_; // don't capture |this| + dispatch_after( + dispatch_time(DISPATCH_TIME_NOW, absl::ToInt64Milliseconds(duration) * NSEC_PER_MSEC), + dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0), ^{ + [impl.queue addOperationWithBlock:^{ + // Execute the runnable only if the executor is not shutting down, and the runnable isn't + // canceled. + // Warning: This block should reference only Obj-C objects, and never C++ objects. + if (!impl.shuttingDown && !wrapper->_canceled->Get()) { + wrapper->_runnable(); + } + }]; + }); + + return std::shared_ptr<api::Cancelable>(cancelable); +} + +void ScheduledExecutor::Execute(Runnable &&runnable) { + DoSubmit(std::move(runnable)); +} + +bool ScheduledExecutor::DoSubmit(Runnable &&runnable) { + if (impl_.shuttingDown) { + return false; + } + + // Submit the runnable to the queue. + Runnable local_runnable = std::move(runnable); + [impl_.queue addOperationWithBlock:^{ + local_runnable(); + }]; + return true; +} + +void ScheduledExecutor::Shutdown(std::int64_t timeout_millis) { + // Prevent new/delayed operations from being queued/executed. + impl_.shuttingDown = YES; + + // Block until either (a) all currently executing operations finish, or (b) the timeout expires. + dispatch_group_t group = dispatch_group_create(); + dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0), ^{ + [impl_.queue waitUntilAllOperationsAreFinished]; + }); + dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, timeout_millis * NSEC_PER_MSEC)); +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/single_thread_executor.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/single_thread_executor.h new file mode 100644 index 00000000000..2979672ddce --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/single_thread_executor.h @@ -0,0 +1,35 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ +#define PLATFORM_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ + +#import "internal/platform/implementation/ios/Source/Platform/multi_thread_executor.h" + +namespace location { +namespace nearby { +namespace ios { + +class SingleThreadExecutor : public MultiThreadExecutor { + public: + SingleThreadExecutor() : MultiThreadExecutor(1) {} + ~SingleThreadExecutor() override = default; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_SINGLE_THREAD_EXECUTOR_H_ + diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/system_clock.cc b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/system_clock.cc new file mode 100644 index 00000000000..db7c722ff88 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/system_clock.cc @@ -0,0 +1,33 @@ +// Copyright 2020 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. + +#include "internal/platform/implementation/system_clock.h" + +#include "absl/time/clock.h" + +namespace location { +namespace nearby { + +void SystemClock::Init() {} + +absl::Time SystemClock::ElapsedRealtime() { return absl::Now(); } + +// TODO(b/169292092): Check the iOS primitive implementation for SystemClock. +Exception SystemClock::Sleep(absl::Duration duration) { + absl::SleepFor(duration); + return {Exception::kSuccess}; +} + +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.h new file mode 100644 index 00000000000..fd864a13f80 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.h @@ -0,0 +1,74 @@ +// Copyright 2020 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 <CoreBluetooth/CoreBluetooth.h> +#import <Foundation/Foundation.h> + +#include <set> +#include <string> + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace location { +namespace nearby { + +/// Converts an Obj-C BOOL to a C++ bool. +bool CppBoolFromObjCBool(BOOL b); + +/// Converts NSNumber to a char. +char CharFromNSNumber(NSNumber *n); + +/// Converts a C++ string to an Obj-C string. +NSString *ObjCStringFromCppString(absl::string_view s); + +/// Converts an Obj-C string to a C++ string. +std::string CppStringFromObjCString(NSString *s); + +/// Converts ByteArray to NSData. +NSData *NSDataFromByteArray(ByteArray byteArray); + +/// Converts NSData to ByteArray. +ByteArray ByteArrayFromNSData(NSData *data); + +/// Converts NSUUID to a C++ string (representing a UUID). +std::string UUIDStringFromNSUUID(NSUUID *uuid); + +/// Converts a C++ string (representing a Bluetooth UUID) to CBUUID. +CBUUID *CBUUIDFromBluetoothUUIDString(absl::string_view bluetoothUUID); + +/// Converts CBUUID to a C++ string (representing a Bluetooth UUID). +std::string BluetoothUUIDStringFromCBUUID(CBUUID *bluetoothUUID); + +/// Converts a C++ set of strings (representing Bluetooth UUIDs) to an NSSet of CBUUID. +NSSet<CBUUID *> *CBUUIDSetFromBluetoothUUIDStringSet(const std::set<std::string> &bluetoothUUIDSet); + +/// Converts an NSSet of CBUUID to a C++ set of strings (representing Bluetooth UUIDs). +std::set<std::string> BluetoothUUIDStringSetFromCBUUIDSet(NSSet<CBUUID *> *bluetoothUUIDSet); + +/// Converts a C++ TxtRecord to an Obj-C TxtRecord. +NSDictionary<NSString *, NSData *> *NSDictionaryFromCppTxtRecords( + const absl::flat_hash_map<std::string, std::string> &txt_records); + +/// Converts an Obj-C TxtRecord to a C++ TxtRecord. +absl::flat_hash_map<std::string, std::string> AbslHashMapFromObjCTxtRecords( + NSDictionary<NSString *, NSData *> *txtRecords); + +} // namespace nearby +} // namespace location + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.mm new file mode 100644 index 00000000000..2dc5bf1525f --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/utils.mm @@ -0,0 +1,104 @@ +// Copyright 2020 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/ios/Source/Platform/utils.h" + +#import "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "internal/platform/byte_array.h" + +NS_ASSUME_NONNULL_BEGIN + +namespace location { +namespace nearby { + +bool CppBoolFromObjCBool(BOOL b) { return b ? true : false; } + +char CharFromNSNumber(NSNumber* n) { return n.charValue; } + +NSString* ObjCStringFromCppString(absl::string_view s) { + return [NSString stringWithUTF8String:s.data()]; +} + +std::string CppStringFromObjCString(NSString* s) { + return std::string([s UTF8String], [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding]); +} + +NSData* NSDataFromByteArray(ByteArray byteArray) { + return [NSData dataWithBytes:byteArray.data() length:byteArray.size()]; +} + +ByteArray ByteArrayFromNSData(NSData* data) { + return ByteArray((const char*)data.bytes, data.length); +} + +std::string UUIDStringFromNSUUID(NSUUID* uuid) { return CppStringFromObjCString(uuid.UUIDString); } + +CBUUID* CBUUIDFromBluetoothUUIDString(absl::string_view bluetoothUUID) { + return [CBUUID UUIDWithString:ObjCStringFromCppString(bluetoothUUID)]; +} + +std::string BluetoothUUIDStringFromCBUUID(CBUUID* bluetoothUUID) { + return CppStringFromObjCString(bluetoothUUID.UUIDString); +} + +NSSet<CBUUID*>* CBUUIDSetFromBluetoothUUIDStringSet(const std::set<std::string>& bluetoothUUIDSet) { + NSMutableSet<CBUUID*>* objcBluetoothUUIDSet = [NSMutableSet set]; + for (std::set<std::string>::const_iterator it = bluetoothUUIDSet.begin(); + it != bluetoothUUIDSet.end(); ++it) { + [objcBluetoothUUIDSet addObject:CBUUIDFromBluetoothUUIDString(*it)]; + } + return objcBluetoothUUIDSet; +} + +std::set<std::string> BluetoothUUIDStringSetFromCBUUIDSet(NSSet<CBUUID*>* bluetoothUUIDSet) { + std::set<std::string> cppBluetoothUUIDSet; + for (CBUUID* bluetoothUUID in bluetoothUUIDSet) { + cppBluetoothUUIDSet.insert(BluetoothUUIDStringFromCBUUID(bluetoothUUID)); + } + return cppBluetoothUUIDSet; +} + +NSDictionary<NSString*, NSData*>* NSDictionaryFromCppTxtRecords( + const absl::flat_hash_map<std::string, std::string>& txt_records) { + NSMutableArray<NSString*>* keyArray = [[NSMutableArray alloc] init]; + NSMutableArray<NSData*>* valueArray = [[NSMutableArray alloc] init]; + for (auto it = txt_records.begin(); it != txt_records.end(); it++) { + NSString* key = @(it->first.c_str()); + NSString* value = @(it->second.c_str()); + [keyArray addObject:key]; + [valueArray addObject:[value dataUsingEncoding:NSUTF8StringEncoding]]; + } + + NSDictionary<NSString*, NSData*>* dict = [[NSDictionary alloc] initWithObjects:valueArray + forKeys:keyArray]; + return dict; +} + +absl::flat_hash_map<std::string, std::string> AbslHashMapFromObjCTxtRecords( + NSDictionary<NSString*, NSData*>* txtRecords) { + absl::flat_hash_map<std::string, std::string> txt_record; + + for (NSString* key in txtRecords) { + NSString* value = [[NSString alloc] initWithData:[txtRecords objectForKey:key] + encoding:NSUTF8StringEncoding]; + txt_record.insert({CppStringFromObjCString(key), CppStringFromObjCString(value)}); + } + return txt_record; +} + +} // namespace nearby +} // namespace location + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.h new file mode 100644 index 00000000000..5a6198b1fd7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.h @@ -0,0 +1,202 @@ +// Copyright 2020 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. + +#ifndef PLATFORM_IMPL_IOS_WIFI_LAN_H_ +#define PLATFORM_IMPL_IOS_WIFI_LAN_H_ + +#import <Foundation/Foundation.h> +#include <string> + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/nsd_service_info.h" +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" // IWYU pragma: export + +@class GNCMBonjourBrowser; +@class GNCMBonjourService; + +namespace location { +namespace nearby { +namespace ios { + +/** InputStream that reads from GNCMConnection. */ +class WifiLanInputStream : public InputStream { + public: + WifiLanInputStream(); + ~WifiLanInputStream() override; + + ExceptionOr<ByteArray> Read(std::int64_t size) override; + Exception Close() override; + + GNCMConnectionHandlers* GetConnectionHandlers() { return connectionHandlers_; } + + private: + GNCMConnectionHandlers* connectionHandlers_; + NSMutableArray<NSData*>* newDataPackets_; + NSMutableData* accumulatedData_; + NSCondition* condition_; +}; + +/** OutputStream that writes to GNCMConnection. */ +class WifiLanOutputStream : public OutputStream { + public: + explicit WifiLanOutputStream(id<GNCMConnection> connection) + : connection_(connection), condition_([[NSCondition alloc] init]) {} + ~WifiLanOutputStream() override; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + id<GNCMConnection> connection_; + NSCondition* condition_; +}; + +/** Concrete WifiLanSocket implementation. */ +class WifiLanSocket : public api::WifiLanSocket { + public: + WifiLanSocket() = default; + explicit WifiLanSocket(id<GNCMConnection> connection); + ~WifiLanSocket() override; + + // api::WifiLanSocket: + InputStream& GetInputStream() override { return *input_stream_; } + OutputStream& GetOutputStream() override { return *output_stream_; } + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_); + + private: + void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + bool closed_ ABSL_GUARDED_BY(mutex_) = false; + std::unique_ptr<WifiLanInputStream> input_stream_; + std::unique_ptr<WifiLanOutputStream> output_stream_; +}; + +/** Concrete WifiLanServerSocket implementation. */ +class WifiLanServerSocket : public api::WifiLanServerSocket { + public: + static std::string GetName(const std::string& ip_address, int port); + + ~WifiLanServerSocket() override; + + // api::WifiLanServerSocket: + std::string GetIPAddress() const override ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + return ip_address_; + } + void SetIPAddress(const std::string& ip_address) ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + ip_address_ = ip_address; + } + int GetPort() const override ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + return port_; + } + void SetPort(int port) ABSL_LOCKS_EXCLUDED(mutex_) { + absl::MutexLock lock(&mutex_); + port_ = port; + } + std::unique_ptr<api::WifiLanSocket> Accept() override ABSL_LOCKS_EXCLUDED(mutex_); + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + bool Connect(std::unique_ptr<WifiLanSocket> socket) ABSL_LOCKS_EXCLUDED(mutex_); + void SetCloseNotifier(std::function<void()> notifier) ABSL_LOCKS_EXCLUDED(mutex_); + + private: + Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + mutable absl::Mutex mutex_; + std::string ip_address_ ABSL_GUARDED_BY(mutex_); + int port_ ABSL_GUARDED_BY(mutex_); + absl::CondVar cond_; + absl::flat_hash_set<std::unique_ptr<WifiLanSocket>> pending_sockets_ ABSL_GUARDED_BY(mutex_); + std::function<void()> close_notifier_ ABSL_GUARDED_BY(mutex_); + bool closed_ ABSL_GUARDED_BY(mutex_) = false; +}; + +/** Concrete WifiLanMedium implementation. */ +class WifiLanMedium : public api::WifiLanMedium { + public: + WifiLanMedium() = default; + ~WifiLanMedium() override; + + WifiLanMedium(const WifiLanMedium&) = delete; + WifiLanMedium& operator=(const WifiLanMedium&) = delete; + + // api::WifiLanMedium: + bool StartAdvertising(const NsdServiceInfo& nsd_service_info) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool StopAdvertising(const NsdServiceInfo& nsd_service_info) override ABSL_LOCKS_EXCLUDED(mutex_); + + bool StartDiscovery(const std::string& service_type, DiscoveredServiceCallback callback) override + ABSL_LOCKS_EXCLUDED(mutex_); + bool StopDiscovery(const std::string& service_type) override ABSL_LOCKS_EXCLUDED(mutex_); + + std::unique_ptr<api::WifiLanSocket> ConnectToService( + const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) override; + std::unique_ptr<api::WifiLanSocket> ConnectToService( + const std::string& ip_address, int port, CancellationFlag* cancellation_flag) override; + std::unique_ptr<api::WifiLanServerSocket> ListenForService(int port) override + ABSL_LOCKS_EXCLUDED(mutex_); + + absl::optional<std::pair<std::int32_t, std::int32_t>> GetDynamicPortRange() override { + return absl::nullopt; + } + + private: + struct AdvertisingInfo { + bool Empty() const { return services.empty(); } + void Clear() { services.clear(); } + void Add(const std::string& service_type, GNCMBonjourService* service) { + services.insert({service_type, service}); + } + void Remove(const std::string& service_type) { services.erase(service_type); } + bool Existed(const std::string& service_type) const { return services.contains(service_type); } + + absl::flat_hash_map<std::string, GNCMBonjourService*> services; + }; + struct DiscoveringInfo { + bool Empty() const { return services.empty(); } + void Clear() { services.clear(); } + void Add(const std::string& service_type, GNCMBonjourBrowser* browser) { + services.insert({service_type, browser}); + } + void Remove(const std::string& service_type) { services.erase(service_type); } + bool Existed(const std::string& service_type) const { return services.contains(service_type); } + + absl::flat_hash_map<std::string, GNCMBonjourBrowser*> services; + }; + + std::string GetFakeIPAddress() const; + int GetFakePort() const; + + absl::Mutex mutex_; + AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_); + int requesting_port_ = 0; + DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map<std::string, WifiLanServerSocket*> server_sockets_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map<std::string, GNCMConnectionRequester> connection_requesters_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_WIFI_LAN_H_ diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.mm new file mode 100644 index 00000000000..debaa4cfaa7 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Platform/wifi_lan.mm @@ -0,0 +1,497 @@ +// Copyright 2020 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/ios/Source/Platform/wifi_lan.h" + +#include <memory> +#include <string> +#include <utility> + +#include "absl/container/flat_hash_map.h" +#include "absl/container/internal/common.h" +#include "absl/memory/memory.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/exception.h" +#import "internal/platform/implementation/ios/Source/Mediums/GNCMConnection.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h" +#import "internal/platform/implementation/ios/Source/Mediums/WifiLan/GNCMBonjourService.h" +#include "internal/platform/implementation/ios/Source/Platform/utils.h" +#include "internal/platform/implementation/wifi_lan.h" +#include "internal/platform/nsd_service_info.h" +#include "internal/platform/prng.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +namespace location { +namespace nearby { +namespace ios { + +/** WifiLanInputStream implementation. */ +WifiLanInputStream::WifiLanInputStream() + : newDataPackets_([NSMutableArray array]), + accumulatedData_([NSMutableData data]), + condition_([[NSCondition alloc] init]) { + // Create the handlers of incoming data from the remote endpoint. + connectionHandlers_ = [GNCMConnectionHandlers + payloadHandler:^(NSData* data) { + [condition_ lock]; + // Add the incoming data to the data packet array to be processed in Read() below. + [newDataPackets_ addObject:data]; + [condition_ signal]; + [condition_ unlock]; + } + disconnectedHandler:^{ + [condition_ lock]; + // Release the data packet array, meaning the stream has been closed or severed. + newDataPackets_ = nil; + [condition_ signal]; + [condition_ unlock]; + }]; +} + +WifiLanInputStream::~WifiLanInputStream() { + NSCAssert(!newDataPackets_, @"WifiLanInputStream not closed before destruction"); +} + +ExceptionOr<ByteArray> WifiLanInputStream::Read(std::int64_t size) { + // Block until either (a) the connection has been closed, or (b) enough data to return. + NSData* dataToReturn; + [condition_ lock]; + while (true) { + // Check if the stream has been closed or severed. + if (!newDataPackets_) break; + + if (newDataPackets_.count > 0) { + // Add the packet data to the accumulated data. + for (NSData* data in newDataPackets_) { + if (data.length > 0) { + [accumulatedData_ appendData:data]; + } + } + [newDataPackets_ removeAllObjects]; + } + + if ((size == -1) && (accumulatedData_.length > 0)) { + // Return all of the data. + dataToReturn = accumulatedData_; + accumulatedData_ = [NSMutableData data]; + break; + } else if (accumulatedData_.length > 0) { + // Return up to |size| bytes of the data. + std::int64_t sizeToReturn = accumulatedData_.length < size ? accumulatedData_.length : size; + NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn); + dataToReturn = [accumulatedData_ subdataWithRange:range]; + [accumulatedData_ replaceBytesInRange:range withBytes:nil length:0]; + break; + } + + [condition_ wait]; + } + [condition_ unlock]; + + if (dataToReturn) { + GTMLoggerInfo(@"[NEARBY] Input stream: Received data of size: %lu", + (unsigned long)dataToReturn.length); + return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn)); + } else { + return ExceptionOr<ByteArray>{Exception::kIo}; + } +} + +Exception WifiLanInputStream::Close() { + // Unblock pending read operation. + [condition_ lock]; + newDataPackets_ = nil; + [condition_ signal]; + [condition_ unlock]; + return {Exception::kSuccess}; +} + +/** WifiLanOutputStream implementation. */ +WifiLanOutputStream::~WifiLanOutputStream() { + NSCAssert(!connection_, @"WifiLanOutputStream not closed before destruction"); +} + +Exception WifiLanOutputStream::Write(const ByteArray& data) { + [condition_ lock]; + GTMLoggerDebug(@"[NEARBY] Sending data of size: %lu", + (unsigned long)NSDataFromByteArray(data).length); + + NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)]; + + // Send the data, blocking until the completion handler is called. + __block GNCMPayloadResult sendResult = GNCMPayloadResultFailure; + __block bool isComplete = NO; + NSCondition* condition = condition_; // don't capture |this| in completion + // Check if connection_ is nil, then just don't wait and return as failure. + if (connection_ != nil) { + [connection_ sendData:packet + progressHandler:^(size_t count) { + } + completion:^(GNCMPayloadResult result) { + // Make sure we haven't already reported completion before. This prevents a crash + // where we try leaving a dispatch group more times than we entered it. + // b/79095653. + if (isComplete) { + return; + } + isComplete = YES; + sendResult = result; + [condition lock]; + [condition signal]; + [condition unlock]; + }]; + [condition_ wait]; + [condition_ unlock]; + } else { + sendResult = GNCMPayloadResultFailure; + [condition_ unlock]; + } + + if (sendResult == GNCMPayloadResultSuccess) { + return {Exception::kSuccess}; + } else { + return {Exception::kIo}; + } +} + +Exception WifiLanOutputStream::Flush() { + // The Write() function block until the data is received by the remote endpoint, so there's + // nothing to do here. + return {Exception::kSuccess}; +} + +Exception WifiLanOutputStream::Close() { + // Unblock pending write operation. + [condition_ lock]; + connection_ = nil; + [condition_ signal]; + [condition_ unlock]; + return {Exception::kSuccess}; +} + +/** WifiLanSocket implementation. */ +WifiLanSocket::WifiLanSocket(id<GNCMConnection> connection) + : input_stream_(new WifiLanInputStream()), + output_stream_(new WifiLanOutputStream(connection)) {} + +WifiLanSocket::~WifiLanSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +bool WifiLanSocket::IsClosed() const { + absl::MutexLock lock(&mutex_); + return closed_; +} + +Exception WifiLanSocket::Close() { + absl::MutexLock lock(&mutex_); + DoClose(); + return {Exception::kSuccess}; +} + +void WifiLanSocket::DoClose() { + if (!closed_) { + input_stream_->Close(); + output_stream_->Close(); + closed_ = true; + } +} + +/** WifiLanServerSocket implementation. */ +std::string WifiLanServerSocket::GetName(const std::string& ip_address, int port) { + std::string dot_delimited_string; + if (!ip_address.empty()) { + for (auto byte : ip_address) { + if (!dot_delimited_string.empty()) absl::StrAppend(&dot_delimited_string, "."); + absl::StrAppend(&dot_delimited_string, absl::StrFormat("%d", byte)); + } + } + std::string out = absl::StrCat(dot_delimited_string, ":", port); + return out; +} + +WifiLanServerSocket::~WifiLanServerSocket() { + absl::MutexLock lock(&mutex_); + DoClose(); +} + +std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() { + absl::MutexLock lock(&mutex_); + while (!closed_ && pending_sockets_.empty()) { + cond_.Wait(&mutex_); + } + // Return early if closed. + if (closed_) return {}; + + auto remote_socket = std::move(pending_sockets_.extract(pending_sockets_.begin()).value()); + return std::move(remote_socket); +} + +bool WifiLanServerSocket::Connect(std::unique_ptr<WifiLanSocket> socket) { + absl::MutexLock lock(&mutex_); + if (closed_) { + return false; + } + // add client socket to the pending list + pending_sockets_.insert(std::move(socket)); + cond_.SignalAll(); + if (closed_) { + return false; + } + return true; +} + +void WifiLanServerSocket::SetCloseNotifier(std::function<void()> notifier) { + absl::MutexLock lock(&mutex_); + close_notifier_ = std::move(notifier); +} + +Exception WifiLanServerSocket::Close() { + absl::MutexLock lock(&mutex_); + return DoClose(); +} + +Exception WifiLanServerSocket::DoClose() { + bool should_notify = !closed_; + closed_ = true; + if (should_notify) { + cond_.SignalAll(); + if (close_notifier_) { + auto notifier = std::move(close_notifier_); + mutex_.Unlock(); + // Notifier may contain calls to public API, and may cause deadlock, if + // mutex_ is held during the call. + notifier(); + mutex_.Lock(); + } + } + return {Exception::kSuccess}; +} + +/** WifiLanMedium implementation. */ +WifiLanMedium::~WifiLanMedium() { + advertising_info_.Clear(); + discovering_info_.Clear(); +} + +bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) { + std::string service_type = nsd_service_info.GetServiceType(); + NSString* serviceType = ObjCStringFromCppString(service_type); + { + absl::MutexLock lock(&mutex_); + if (advertising_info_.Existed(service_type)) { + GTMLoggerInfo(@"[NEARBY] WifiLan StartAdvertising: Can't start advertising because " + @"service_type=%@, has started already", + serviceType); + return false; + } + } + + // Retrieve service name. + NSString* serviceName = ObjCStringFromCppString(nsd_service_info.GetServiceName()); + // Retrieve TXTRecord and convert it to NSDictionary type. + NSDictionary<NSString*, NSData*>* TXTRecordData = + NSDictionaryFromCppTxtRecords(nsd_service_info.GetTxtRecords()); + // Get ip address and port to retrieve the server_socket, if not, then return nil. + std::string ip_address = nsd_service_info.GetIPAddress(); + int port = nsd_service_info.GetPort(); + __block std::string socket_name = WifiLanServerSocket::GetName(ip_address, port); + GNCMBonjourService* service = [[GNCMBonjourService alloc] + initWithServiceName:serviceName + serviceType:serviceType + port:requesting_port_ + TXTRecordData:TXTRecordData + endpointConnectedHandler:^GNCMConnectionHandlers*(id<GNCMConnection> connection) { + auto item = server_sockets_.find(socket_name); + WifiLanServerSocket* server_socket = item != server_sockets_.end() ? item->second : nullptr; + if (!server_socket) { + return nil; + } + auto socket = absl::make_unique<WifiLanSocket>(connection); + GNCMConnectionHandlers* connectionHandlers = + static_cast<WifiLanInputStream&>(socket->GetInputStream()).GetConnectionHandlers(); + server_socket->Connect(std::move(socket)); + return connectionHandlers; + }]; + { + absl::MutexLock lock(&mutex_); + advertising_info_.Add(service_type, service); + } + return true; +} + +bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) { + std::string service_type = nsd_service_info.GetServiceType(); + { + absl::MutexLock lock(&mutex_); + if (!advertising_info_.Existed(service_type)) { + GTMLoggerInfo(@"[NEARBY] WifiLan StopAdvertising: Can't stop advertising because we never " + @"started advertising for service_type=%@", + ObjCStringFromCppString(service_type)); + return false; + } + advertising_info_.Remove(service_type); + } + return true; +} + +bool WifiLanMedium::StartDiscovery(const std::string& service_type, + DiscoveredServiceCallback callback) { + NSString* serviceType = ObjCStringFromCppString(service_type); + { + absl::MutexLock lock(&mutex_); + if (discovering_info_.Existed(service_type)) { + GTMLoggerInfo(@"[NEARBY] WifiLan StartAdvertising: Can't start discovery because " + @"service_type=%@, has started already", + serviceType); + return false; + } + } + GNCMBonjourBrowser* browser = [[GNCMBonjourBrowser alloc] + initWithServiceType:serviceType + endpointFoundHandler:^GNCMEndpointLostHandler( + NSString* endpointId, NSString* serviceType, NSString* serviceName, + NSDictionary<NSString*, NSData*>* _Nullable txtRecordData, + GNCMConnectionRequester requestConnection) { + __block NsdServiceInfo nsd_service_info = {}; + nsd_service_info.SetServiceName(CppStringFromObjCString(serviceName)); + nsd_service_info.SetServiceType(CppStringFromObjCString(serviceType)); + // Set TXTRecord converted from NSDictionary to hash map. + if (txtRecordData != nil) { + auto txt_records = AbslHashMapFromObjCTxtRecords(txtRecordData); + nsd_service_info.SetTxtRecords(txt_records); + } + connection_requesters_.insert({CppStringFromObjCString(serviceType), requestConnection}); + callback.service_discovered_cb(nsd_service_info); + return ^{ + callback.service_lost_cb(nsd_service_info); + }; + }]; + { + absl::MutexLock lock(&mutex_); + discovering_info_.Add(service_type, browser); + } + return true; +} + +bool WifiLanMedium::StopDiscovery(const std::string& service_type) { + { + absl::MutexLock lock(&mutex_); + if (!discovering_info_.Existed(service_type)) { + GTMLoggerInfo(@"[NEARBY] WifiLan StopDiscovery: Can't stop discovering because " + "we never started discovering."); + return false; + } + discovering_info_.Remove(service_type); + } + return true; +} + +std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService( + const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) { + std::string service_type = remote_service_info.GetServiceType(); + GTMLoggerInfo(@"[NEARBY] WifiLan ConnectToService, service_type=%@", + ObjCStringFromCppString(service_type)); + + GNCMConnectionRequester connection_requester = nil; + { + absl::MutexLock lock(&mutex_); + const auto& it = connection_requesters_.find(service_type); + if (it == connection_requesters_.end()) { + return {}; + } + connection_requester = it->second; + } + + dispatch_group_t group = dispatch_group_create(); + dispatch_group_enter(group); + __block std::unique_ptr<WifiLanSocket> socket; + if (connection_requester != nil) { + if (cancellation_flag->Cancelled()) { + GTMLoggerError(@"[NEARBY] WifiLan Connect: Has been cancelled: service_type=%@", + ObjCStringFromCppString(service_type)); + dispatch_group_leave(group); // unblock + return {}; + } + + connection_requester(^(id<GNCMConnection> connection) { + // If the connection wasn't successfully established, return a NULL socket. + if (connection) { + socket = absl::make_unique<WifiLanSocket>(connection); + } + + dispatch_group_leave(group); // unblock + return socket != nullptr ? static_cast<WifiLanInputStream&>(socket->GetInputStream()) + .GetConnectionHandlers() + : nullptr; + }); + } + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + return std::move(socket); +} + +std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService( + const std::string& ip_address, int port, CancellationFlag* cancellation_flag) { + // Not implemented. + return {}; +} + +std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(int port) { + auto server_socket = std::make_unique<WifiLanServerSocket>(); + // The fake ip address and port need to be set here since they can't be retrieved before + // StartAadvertising begins. Furthermore, NSNetService can't resolve ip address when finding + // service. Try to fake them and make it socket name as a key of server_sockets_ which acts + // the same socket binding. + server_socket->SetIPAddress(GetFakeIPAddress()); + requesting_port_ = port; + server_socket->SetPort(requesting_port_ == 0 ? GetFakePort() : requesting_port_); + std::string socket_name = + WifiLanServerSocket::GetName(server_socket->GetIPAddress(), server_socket->GetPort()); + server_socket->SetCloseNotifier([this, socket_name]() { + absl::MutexLock lock(&mutex_); + server_sockets_.erase(socket_name); + }); + GTMLoggerInfo(@"[NEARBY] WifiLan Adding server socket, socket_name=%@", + ObjCStringFromCppString(socket_name)); + absl::MutexLock lock(&mutex_); + server_sockets_.insert({socket_name, server_socket.get()}); + return server_socket; +} + +std::string WifiLanMedium::GetFakeIPAddress() const { + std::string ip_address; + ip_address.resize(4); + uint32_t raw_ip_addr = Prng().NextUint32(); + ip_address[0] = static_cast<char>(raw_ip_addr >> 24); + ip_address[1] = static_cast<char>(raw_ip_addr >> 16); + ip_address[2] = static_cast<char>(raw_ip_addr >> 8); + ip_address[3] = static_cast<char>(raw_ip_addr >> 0); + + return ip_address; +} + +int WifiLanMedium::GetFakePort() const { + uint16_t port = Prng().NextUint32(); + + return port; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/BUILD b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/BUILD new file mode 100644 index 00000000000..1ad2cda37f2 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/BUILD @@ -0,0 +1,30 @@ +# Copyright 2020 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. +licenses(["notice"]) + +package(default_visibility = ["//internal/platform/implementation/ios:__subpackages__"]) + +objc_library( + name = "Shared", + srcs = [ + "GNCUtils.m", + ], + hdrs = [ + "GNCUtils.h", + ], + deps = [ + "//third_party/objective_c/google_toolbox_for_mac:GTM_StringEncoding", + "@aappleby_smhasher//:libmurmur3", + ], +) diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.h b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.h new file mode 100644 index 00000000000..0d27c550978 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.h @@ -0,0 +1,50 @@ +// Copyright 2020 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 <Foundation/Foundation.h> + +NS_ASSUME_NONNULL_BEGIN + +// Current version of socket protocol. +extern const int GNCSocketVersion; + +#ifdef __cplusplus +extern "C" { +#endif + +/// Generates a SHA-256 hash (32 bytes) from a data object. +NSData *_Nullable GNCSha256Data(NSData *data); + +/// Generates a SHA-256 hash (32 bytes) from a string. +NSData *_Nullable GNCSha256String(NSString *string); + + +/// Generates an MD5 hash (16 bytes) from a data object. +NSData *_Nullable GNCMd5Data(NSData *data); + +/// Generates an MD5 hash (16 bytes) from a string. +NSData *_Nullable GNCMd5String(NSString *string); + + +/// Base64-encode. +NSString *GNCBase64Encode(NSData *data); + +/// Base64-decode. +NSData *GNCBase64Decode(NSString *string); + +#ifdef __cplusplus +} // extern "C" +#endif + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.m b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.m new file mode 100644 index 00000000000..ac2bba83386 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Source/Shared/GNCUtils.m @@ -0,0 +1,65 @@ +// Copyright 2020 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/ios/Source/Shared/GNCUtils.h" + +#import <CommonCrypto/CommonDigest.h> + +#import "GoogleToolboxForMac/GTMStringEncoding.h" + +NS_ASSUME_NONNULL_BEGIN + +const int GNCSocketVersion = 2; + +NSData *GNCSha256Data(NSData *data) { + unsigned char output[CC_SHA256_DIGEST_LENGTH]; + CC_LONG length = (CC_LONG)data.length; + if (!CC_SHA256(data.bytes, length, output)) return nil; + return [NSData dataWithBytes:output length:CC_SHA256_DIGEST_LENGTH]; +} + +NSData *GNCSha256String(NSString *string) { + return GNCSha256Data([string dataUsingEncoding:NSUTF8StringEncoding]); +} + +NSData *GNCMd5Data(NSData *data) { + unsigned char md5Buffer[CC_MD5_DIGEST_LENGTH]; + CC_MD5(data.bytes, (CC_LONG)data.length, md5Buffer); + return [NSData dataWithBytes:md5Buffer length:CC_MD5_DIGEST_LENGTH]; +} + +NSData *GNCMd5String(NSString *string) { + return GNCMd5Data([string dataUsingEncoding:NSUTF8StringEncoding]); +} + +static GTMStringEncoding *GNCBase64WebSafeEncodingNoPadding() { + static dispatch_once_t onceToken; + static GTMStringEncoding *encoding; + dispatch_once(&onceToken, ^{ + encoding = [GTMStringEncoding stringEncodingWithString: + @"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"]; + [encoding setDoPad:NO]; + }); + return encoding; +} + +NSString *GNCBase64Encode(NSData *data) { + return [GNCBase64WebSafeEncodingNoPadding() encode:data error:nil]; +} + +NSData *GNCBase64Decode(NSString *string) { + return [GNCBase64WebSafeEncodingNoPadding() decode:string error:nil]; +} + +NS_ASSUME_NONNULL_END diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/BUILD b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/BUILD new file mode 100644 index 00000000000..4b09d7d57bb --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/BUILD @@ -0,0 +1,63 @@ +load("//tools/build_defs/apple:ios.bzl", "ios_unit_test") + +# Copyright 2020 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. +licenses(["notice"]) + +package(default_visibility = ["//visibility:public"]) + +objc_library( + name = "PlatformTestslib", + testonly = 1, + srcs = [ + "Platform/GNCCryptoTest.mm", + "Platform/GNCInputFileTest.mm", + "Platform/GNCMultiThreadExecutorTest.mm", + "Platform/GNCScheduledExecutorTest.mm", + "Platform/GNCSingleThreadExecutorTest.mm", + ], + deps = [ + "//internal/platform/implementation/ios:Connections", + ], +) + +ios_unit_test( + name = "PlatformTests", + minimum_os_version = "12.0", + runner = "//testing/utp/ios:IOS_12", + deps = [ + ":PlatformTestslib", + ], +) + +objc_library( + name = "SharedTestslib", + testonly = 1, + srcs = [ + "Shared/GNCUtilsTest.mm", + ], + deps = [ + "//internal/platform/implementation/ios/Source/Platform", + "//internal/platform/implementation/ios/Source/Shared", + ], +) + +ios_unit_test( + name = "SharedTests", + minimum_os_version = "12.0", + runner = "//testing/utp/ios:IOS_12", + deps = [ + ":SharedTestslib", + ], +) diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCCryptoTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCCryptoTest.mm new file mode 100644 index 00000000000..3273325bb72 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCCryptoTest.mm @@ -0,0 +1,52 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#include "internal/platform/implementation/crypto.h" +#include "internal/platform/byte_array.h" + +@interface GNCCryptoTest : XCTestCase +@end + +@implementation GNCCryptoTest + +// Tests that the hash functions return the expected values. +- (void)testHashValues { + std::string string("com.google.location.nearby"); + + // SHA-256 test. + { + const uint8_t sha256ExpectedHash[] = { + 0x09, 0x8e, 0x3c, 0x54, 0x2d, 0xee, 0x9e, 0x35, + 0x1d, 0xe7, 0x5b, 0xcf, 0xda, 0xb5, 0x62, 0xd3, + 0xde, 0xba, 0x14, 0x49, 0xbd, 0xf5, 0x95, 0x04, + 0x1f, 0x1d, 0x99, 0x84, 0x87, 0xc3, 0xcb, 0x8a, }; + location::nearby::ByteArray sha256Hash = location::nearby::Crypto::Sha256(string); + XCTAssert(sha256Hash.size() == sizeof(sha256ExpectedHash) && + memcmp(sha256Hash.data(), sha256ExpectedHash, sizeof(sha256ExpectedHash)) == 0); + } + + // MD5 test. + { + const uint8_t md5ExpectedHash[] = { + 0x97, 0x78, 0x1d, 0x3d, 0xee, 0xd7, 0xdc, 0x5a, + 0x6e, 0xee, 0x50, 0x08, 0xce, 0xd1, 0xb2, 0xe8 }; + location::nearby::ByteArray md5Hash = location::nearby::Crypto::Md5(string); + XCTAssert(md5Hash.size() == sizeof(md5ExpectedHash) && + memcmp(md5Hash.data(), md5ExpectedHash, sizeof(md5ExpectedHash)) == 0); + } +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCInputFileTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCInputFileTest.mm new file mode 100644 index 00000000000..e744de067a1 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCInputFileTest.mm @@ -0,0 +1,56 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#import "internal/platform/byte_array.h" +#import "internal/platform/exception.h" +#import "internal/platform/implementation/ios/Source/Platform/input_file.h" + +using ::location::nearby::ios::InputFile; +using ::location::nearby::ByteArray; +using ::location::nearby::ExceptionOr; + +@interface GNCInputFileTest : XCTestCase +@end + +@implementation GNCInputFileTest + +// TODO(b/169292092): Find more tools (e.g. file/util/TempPath) to test fake file. +- (void)testNonExistentPath { + std::string cc_path("/foo/bar/test.ext"); + NSString* objcPath = [NSString stringWithUTF8String:cc_path.c_str()]; + NSURL *testURL = [NSURL URLWithString:objcPath]; + + auto input_file = std::make_unique<InputFile>(testURL); + XCTAssert(input_file != nullptr); + + XCTAssertEqual(input_file->GetTotalSize(), 0); + ExceptionOr<ByteArray> read_result = input_file->Read(3); + XCTAssertFalse(read_result.ok()); +} + +- (void)testGetFilePath { + std::string cc_path("/foo/bar/test.ext"); + NSString* objcPath = [NSString stringWithUTF8String:cc_path.c_str()]; + NSURL *testURL = [NSURL URLWithString:objcPath]; + + auto input_file = std::make_unique<InputFile>(testURL); + XCTAssert(input_file != nullptr); + + std::string input_file_path = input_file->GetFilePath(); + XCTAssertTrue([objcPath isEqualToString:[NSString stringWithUTF8String:input_file_path.c_str()]]); +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm new file mode 100644 index 00000000000..c5439fcffe5 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm @@ -0,0 +1,114 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#include "absl/time/time.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/executor.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/submittable_executor.h" +#include "internal/platform/runnable.h" + +using location::nearby::Runnable; +using location::nearby::api::ImplementationPlatform; +using MultiThreadExecutor = location::nearby::api::SubmittableExecutor; + +@interface GNCMultiThreadExecutorTest : XCTestCase +@property(atomic) int counter; +@property(atomic) int otherCounter; +@end + +@implementation GNCMultiThreadExecutorTest + +// Creates a MultiThreadExecutor. +- (std::unique_ptr<MultiThreadExecutor>)executor { + std::unique_ptr<MultiThreadExecutor> executor = + ImplementationPlatform::CreateMultiThreadExecutor(4); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor executes runnables as expected. +- (void)testExecute { + std::unique_ptr<MultiThreadExecutor> executor([self executor]); + + // Execute two runnables that increment the counter. + const int kIncrements = 13; + Runnable incrementer = [self]() { self.counter++; }; + for (int i = 0; i < kIncrements; i++) { + executor->Execute(std::move(incrementer)); + } + + // Check that the counter has the expected value after giving the runnables time to run. + [NSThread sleepForTimeInterval:0.01]; + XCTAssertEqual(self.counter, kIncrements); +} + +// Tests that the executor submits runnables as expected. +- (void)testSubmit { + std::unique_ptr<MultiThreadExecutor> executor([self executor]); + + // Submit two runnables that increment the counter. + const int kIncrements = 13; + Runnable incrementer = [self]() { self.counter++; }; + for (int i = 0; i < kIncrements; i++) { + executor->DoSubmit(std::move(incrementer)); + } + + // Check that the counter has the expected value after giving the runnables time to run. + [NSThread sleepForTimeInterval:0.01]; + XCTAssertEqual(self.counter, kIncrements); +} + +// Tests that fails to submit when the executor is shut down. +- (void)testFailtoSubmitAfterShutdown { + std::unique_ptr<MultiThreadExecutor> executor([self executor]); + + executor->Shutdown(); + + XCTAssertFalse(executor->DoSubmit([self]() { self.counter++; })); + + // Check that the counter has the expected value after giving the runnables time to run. + [NSThread sleepForTimeInterval:0.01]; + XCTAssertEqual(self.counter, 0); +} + +// Tests that when the executor is shut down, it waits for pending operations to finish, and no new +// operations will be executed. +- (void)testShutdownToAllowExistingTaskComplete { + std::unique_ptr<MultiThreadExecutor> executor([self executor]); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0); + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + const int kRunnableCount = 1000; + for (int i = 0; i < kRunnableCount; i++) { + executor->Execute([self]() { self.counter++; }); + } + + executor->Shutdown(); + + executor->Execute([self]() { self.otherCounter++; }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{ + XCTAssertLessThanOrEqual(self.counter, kRunnableCount); + XCTAssertEqual(self.otherCounter, 0); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:0.5 handler:nil]; +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCScheduledExecutorTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCScheduledExecutorTest.mm new file mode 100644 index 00000000000..9f00bc47391 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCScheduledExecutorTest.mm @@ -0,0 +1,139 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#include "absl/time/time.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/executor.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/scheduled_executor.h" +#include "internal/platform/runnable.h" + +using location::nearby::Runnable; +using location::nearby::api::ImplementationPlatform; +using location::nearby::api::ScheduledExecutor; + +@interface GNCScheduledExecutorTest : XCTestCase +@property(atomic) int counter; +@end + +@implementation GNCScheduledExecutorTest + +// Creates a ScheduledExecutor. +- (std::unique_ptr<ScheduledExecutor>)executor { + std::unique_ptr<ScheduledExecutor> executor = ImplementationPlatform::CreateScheduledExecutor(); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor schedules operation at the specified time. +- (void)testScheduling { + std::unique_ptr<ScheduledExecutor> executor([self executor]); + + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + Runnable incrementer = [self]() { self.counter++; }; + + void (^checkCounter)(int, NSTimeInterval, dispatch_block_t) = + ^(int expectedCount, NSTimeInterval delay, dispatch_block_t finalBlock) { + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)), + dispatch_get_main_queue(), ^{ + XCTAssertEqual(self.counter, expectedCount); + finalBlock(); + }); + }; + + // Schedule two runnables that increment the counter, at 0.4 and 0.8 seconds. + executor->Schedule(std::move(incrementer), absl::Seconds(0.4)); + executor->Schedule(std::move(incrementer), absl::Seconds(0.8)); + + // Check that the counter contains the expected values at 0.2, 0.6, and 1.0 seconds. + checkCounter(0, 0.2, ^{}); + checkCounter(1, 0.6, ^{}); + checkCounter(2, 1.0, ^{ [expectation fulfill]; }); + + [self waitForExpectationsWithTimeout:1.2 handler:nil]; +} + +// Tests that fails to schedule when the executor is shut down. +- (void)testFailtoScheduleAfterShutdown { + std::unique_ptr<ScheduledExecutor> executor([self executor]); + + executor->Shutdown(); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0); + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + const NSTimeInterval delay = 0.1; + executor->Schedule([self]() { self.counter++; }, absl::Milliseconds(delay)); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * 2 * NSEC_PER_SEC)), queue, ^{ + XCTAssertEqual(self.counter, 0); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:delay * 5 handler:nil]; +} + +// Tests that fails to cancel when the executor is shut down. +- (void)testFailToCancelAfterShutdown { + std::unique_ptr<ScheduledExecutor> executor([self executor]); + + executor->Shutdown(); + + auto cancelable = executor->Schedule([self]() { self.counter++; }, absl::Seconds(0.1)); + + XCTAssert(cancelable.get() == nullptr); +} + +// Tests that shutting down an existing task fails to complete. +- (void)testShutdownToFailExistingTask { + std::unique_ptr<ScheduledExecutor> executor([self executor]); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0); + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + const NSTimeInterval delay = 0.1; + executor->Schedule([self]() { self.counter++; }, absl::Milliseconds(delay)); + + executor->Shutdown(); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * 2 * NSEC_PER_SEC)), queue, ^{ + XCTAssertEqual(self.counter, 0); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:delay * 5 handler:nil]; +} + +// Tests that a canceled runnable doesn't run. +- (void)testCancelable { + std::unique_ptr<ScheduledExecutor> executor([self executor]); + + // This runnable should run but not increment the counter because it sleeps for longer than the + // cancel below. + const NSTimeInterval delay = 0.1; + auto cancelable = executor->Schedule([self]() { self.counter++; }, absl::Seconds(delay)); + XCTAssert(cancelable.get() != nullptr); + + // Cancel the runnable. + cancelable->Cancel(); + + // Wait for the time interval to pass and verify that it didn't run. + [NSThread sleepForTimeInterval:delay * 1.1]; + XCTAssertEqual(self.counter, 0); +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm new file mode 100644 index 00000000000..31686f804ab --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm @@ -0,0 +1,95 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#include "absl/time/time.h" +#include "internal/platform/implementation/cancelable.h" +#include "internal/platform/implementation/executor.h" +#include "internal/platform/implementation/platform.h" +#include "internal/platform/implementation/submittable_executor.h" +#include "internal/platform/runnable.h" + +using location::nearby::Runnable; +using location::nearby::api::ImplementationPlatform; +using SingleThreadExecutor = location::nearby::api::SubmittableExecutor; + +@interface GNCSingleThreadExecutorTest : XCTestCase +@property(atomic) int counter; +@end + +@implementation GNCSingleThreadExecutorTest + +// Creates a SingleThreadExecutor. +- (std::unique_ptr<SingleThreadExecutor>)executor { + std::unique_ptr<SingleThreadExecutor> executor = + ImplementationPlatform::CreateSingleThreadExecutor(); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor executes runnables as expected. +- (void)testRunnables { + std::unique_ptr<SingleThreadExecutor> executor([self executor]); + + Runnable incrementer = [self]() { self.counter++; }; + + // Schedule two runnables that increment the counter. + executor->Execute(std::move(incrementer)); + executor->Execute(std::move(incrementer)); + + // Check that the counter has the expected value after a moment. + [NSThread sleepForTimeInterval:0.01]; + XCTAssertEqual(self.counter, 2); +} + +// Tests that fails to execute when the executor is shut down. +- (void)testShutdownBeforeInvokeTask { + std::unique_ptr<SingleThreadExecutor> executor([self executor]); + + executor->Shutdown(); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0); + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + executor->Execute([self]() { self.counter++; }); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{ + XCTAssertEqual(self.counter, 0); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:0.5 handler:nil]; +} + +// Tests that shutting down an existing task allows to complete. +- (void)testShutdownToAllowExistingTaskComplete { + std::unique_ptr<SingleThreadExecutor> executor([self executor]); + + dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0); + XCTestExpectation *expectation = [self expectationWithDescription:@"finished"]; + + executor->Execute([self]() { self.counter++; }); + + executor->Shutdown(); + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{ + XCTAssertEqual(self.counter, 1); + [expectation fulfill]; + }); + + [self waitForExpectationsWithTimeout:0.5 handler:nil]; +} + +@end diff --git a/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Shared/GNCUtilsTest.mm b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Shared/GNCUtilsTest.mm new file mode 100644 index 00000000000..6773f10f5d9 --- /dev/null +++ b/chromium/third_party/nearby/src/internal/platform/implementation/ios/Tests/Shared/GNCUtilsTest.mm @@ -0,0 +1,46 @@ +// Copyright 2020 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 <XCTest/XCTest.h> + +#include "internal/platform/byte_array.h" +#include "internal/platform/implementation/ios/Source/Platform/utils.h" + +using location::nearby::ByteArray; +using location::nearby::ByteArrayFromNSData; +using location::nearby::CppStringFromObjCString; +using location::nearby::ObjCStringFromCppString; + +@interface GNCUtilsTest : XCTestCase +@end + +@implementation GNCUtilsTest + +// Tests that strings can be converted between C++ and Obj-C. +- (void)testStrings { + XCTAssert([ObjCStringFromCppString(std::string("Hey")) isEqual:@"Hey"]); + XCTAssert(CppStringFromObjCString(@"Dude") == std::string("Dude")); +} + +// Tests that data objects can be converted between C++ and Obj-C. +- (void)testDataObjects { + uint8_t bytes[] = {0xfe, 0xed, 0xfa, 0xce, 0xde, 0xad, 0xbe, 0xef}; + ByteArray byteArray = ByteArray{(const char *)bytes, sizeof(bytes)}; + NSData *nsData = [NSData dataWithBytes:bytes length:sizeof(bytes)]; + + XCTAssert([NSDataFromByteArray(byteArray) isEqual:nsData]); + XCTAssert(memcmp(ByteArrayFromNSData(nsData).data(), bytes, sizeof(bytes)) == 0); +} + +@end |