From fb0337ebfa8791d9dfb7aa1af5ef842d92a0be19 Mon Sep 17 00:00:00 2001 From: edwinwu Date: Sat, 6 Nov 2021 09:44:49 -0700 Subject: [PATCH] ios: nearbyConnections: Move source to //third_party. PiperOrigin-RevId: 408046875 --- cpp/core/BUILD | 4 +- cpp/platform/api/BUILD | 1 - cpp/platform/base/BUILD | 1 - cpp/platform/impl/ios/BUILD | 52 ++ cpp/platform/impl/ios/Source/GNCAdvertiser.h | 84 +++ cpp/platform/impl/ios/Source/GNCConnection.h | 167 ++++++ cpp/platform/impl/ios/Source/GNCConnections.h | 20 + cpp/platform/impl/ios/Source/GNCDiscoverer.h | 92 ++++ cpp/platform/impl/ios/Source/GNCPayload.h | 62 +++ .../impl/ios/Source/Internal/GNCAdvertiser.mm | 313 +++++++++++ .../impl/ios/Source/Internal/GNCCore.h | 55 ++ .../impl/ios/Source/Internal/GNCCore.mm | 94 ++++ .../ios/Source/Internal/GNCCoreConnection.h | 44 ++ .../ios/Source/Internal/GNCCoreConnection.mm | 188 +++++++ .../impl/ios/Source/Internal/GNCDiscoverer.mm | 394 ++++++++++++++ .../ios/Source/Internal/GNCPayload+Internal.h | 36 ++ .../impl/ios/Source/Internal/GNCPayload.m | 88 ++++ .../ios/Source/Internal/GNCPayloadListener.h | 55 ++ .../ios/Source/Internal/GNCPayloadListener.mm | 218 ++++++++ .../impl/ios/Source/Internal/GNCUtils.h | 43 ++ .../impl/ios/Source/Internal/GNCUtils.mm | 77 +++ .../impl/ios/Source/Internal/platform.mm | 151 ++++++ cpp/platform/impl/ios/Source/Mediums/BUILD | 57 ++ .../impl/ios/Source/Mediums/GNCLeaks.h | 18 + .../impl/ios/Source/Mediums/GNCLeaks.m | 27 + .../impl/ios/Source/Mediums/GNCMConnection.h | 101 ++++ .../impl/ios/Source/Mediums/GNCMConnection.m | 31 ++ .../Mediums/WifiLan/GNCMBonjourBrowser.h | 42 ++ .../Mediums/WifiLan/GNCMBonjourBrowser.m | 176 +++++++ .../Mediums/WifiLan/GNCMBonjourConnection.h | 38 ++ .../Mediums/WifiLan/GNCMBonjourConnection.m | 207 ++++++++ .../Mediums/WifiLan/GNCMBonjourService.h | 49 ++ .../Mediums/WifiLan/GNCMBonjourService.m | 88 ++++ .../Source/Mediums/WifiLan/GNCMBonjourUtils.h | 18 + .../Source/Mediums/WifiLan/GNCMBonjourUtils.m | 17 + cpp/platform/impl/ios/Source/Platform/BUILD | 93 ++++ .../impl/ios/Source/Platform/atomic_boolean.h | 46 ++ .../Source/Platform/atomic_boolean_test.cc | 78 +++ .../impl/ios/Source/Platform/atomic_uint32.h | 47 ++ .../ios/Source/Platform/atomic_uint32_test.cc | 64 +++ .../ios/Source/Platform/condition_variable.cc | 37 ++ .../ios/Source/Platform/condition_variable.h | 48 ++ .../Platform/condition_variable_test.cc | 84 +++ .../ios/Source/Platform/count_down_latch.cc | 40 ++ .../ios/Source/Platform/count_down_latch.h | 49 ++ .../Source/Platform/count_down_latch_test.cc | 83 +++ .../impl/ios/Source/Platform/crypto.mm | 39 ++ .../impl/ios/Source/Platform/input_file.h | 48 ++ .../impl/ios/Source/Platform/input_file.mm | 69 +++ .../impl/ios/Source/Platform/log_message.h | 47 ++ .../impl/ios/Source/Platform/log_message.mm | 87 +++ .../Source/Platform/multi_thread_executor.h | 47 ++ .../Source/Platform/multi_thread_executor.mm | 40 ++ cpp/platform/impl/ios/Source/Platform/mutex.h | 84 +++ .../impl/ios/Source/Platform/mutex_test.cc | 92 ++++ .../ios/Source/Platform/scheduled_executor.h | 68 +++ .../ios/Source/Platform/scheduled_executor.mm | 144 +++++ .../Source/Platform/single_thread_executor.h | 35 ++ .../impl/ios/Source/Platform/system_clock.cc | 33 ++ cpp/platform/impl/ios/Source/Platform/utils.h | 74 +++ .../impl/ios/Source/Platform/utils.mm | 104 ++++ .../impl/ios/Source/Platform/wifi_lan.h | 198 +++++++ .../impl/ios/Source/Platform/wifi_lan.mm | 497 ++++++++++++++++++ cpp/platform/impl/ios/Source/Shared/BUILD | 30 ++ .../impl/ios/Source/Shared/GNCUtils.h | 50 ++ .../impl/ios/Source/Shared/GNCUtils.m | 65 +++ cpp/platform/impl/ios/Tests/BUILD | 64 +++ .../impl/ios/Tests/Platform/GNCCryptoTest.mm | 52 ++ .../ios/Tests/Platform/GNCInputFileTest.mm | 56 ++ .../Platform/GNCMultiThreadExecutorTest.mm | 114 ++++ .../Platform/GNCScheduledExecutorTest.mm | 139 +++++ .../Platform/GNCSingleThreadExecutorTest.mm | 95 ++++ .../impl/ios/Tests/Shared/GNCUtilsTest.mm | 46 ++ cpp/platform/impl/shared/BUILD | 1 - cpp/platform/public/BUILD | 2 +- proto/connections/BUILD | 2 +- 76 files changed, 6192 insertions(+), 7 deletions(-) create mode 100644 cpp/platform/impl/ios/BUILD create mode 100644 cpp/platform/impl/ios/Source/GNCAdvertiser.h create mode 100644 cpp/platform/impl/ios/Source/GNCConnection.h create mode 100644 cpp/platform/impl/ios/Source/GNCConnections.h create mode 100644 cpp/platform/impl/ios/Source/GNCDiscoverer.h create mode 100644 cpp/platform/impl/ios/Source/GNCPayload.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCAdvertiser.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCCore.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCCore.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCDiscoverer.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCPayload+Internal.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCPayload.m create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCUtils.h create mode 100644 cpp/platform/impl/ios/Source/Internal/GNCUtils.mm create mode 100644 cpp/platform/impl/ios/Source/Internal/platform.mm create mode 100644 cpp/platform/impl/ios/Source/Mediums/BUILD create mode 100644 cpp/platform/impl/ios/Source/Mediums/GNCLeaks.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/GNCLeaks.m create mode 100644 cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/GNCMConnection.m create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.m create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h create mode 100644 cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m create mode 100644 cpp/platform/impl/ios/Source/Platform/BUILD create mode 100644 cpp/platform/impl/ios/Source/Platform/atomic_boolean.h create mode 100644 cpp/platform/impl/ios/Source/Platform/atomic_boolean_test.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/atomic_uint32.h create mode 100644 cpp/platform/impl/ios/Source/Platform/atomic_uint32_test.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/condition_variable.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/condition_variable.h create mode 100644 cpp/platform/impl/ios/Source/Platform/condition_variable_test.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/count_down_latch.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/count_down_latch.h create mode 100644 cpp/platform/impl/ios/Source/Platform/count_down_latch_test.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/crypto.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/input_file.h create mode 100644 cpp/platform/impl/ios/Source/Platform/input_file.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/log_message.h create mode 100644 cpp/platform/impl/ios/Source/Platform/log_message.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/multi_thread_executor.h create mode 100644 cpp/platform/impl/ios/Source/Platform/multi_thread_executor.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/mutex.h create mode 100644 cpp/platform/impl/ios/Source/Platform/mutex_test.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/scheduled_executor.h create mode 100644 cpp/platform/impl/ios/Source/Platform/scheduled_executor.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/single_thread_executor.h create mode 100644 cpp/platform/impl/ios/Source/Platform/system_clock.cc create mode 100644 cpp/platform/impl/ios/Source/Platform/utils.h create mode 100644 cpp/platform/impl/ios/Source/Platform/utils.mm create mode 100644 cpp/platform/impl/ios/Source/Platform/wifi_lan.h create mode 100644 cpp/platform/impl/ios/Source/Platform/wifi_lan.mm create mode 100644 cpp/platform/impl/ios/Source/Shared/BUILD create mode 100644 cpp/platform/impl/ios/Source/Shared/GNCUtils.h create mode 100644 cpp/platform/impl/ios/Source/Shared/GNCUtils.m create mode 100644 cpp/platform/impl/ios/Tests/BUILD create mode 100644 cpp/platform/impl/ios/Tests/Platform/GNCCryptoTest.mm create mode 100644 cpp/platform/impl/ios/Tests/Platform/GNCInputFileTest.mm create mode 100644 cpp/platform/impl/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm create mode 100644 cpp/platform/impl/ios/Tests/Platform/GNCScheduledExecutorTest.mm create mode 100644 cpp/platform/impl/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm create mode 100644 cpp/platform/impl/ios/Tests/Shared/GNCUtilsTest.mm diff --git a/cpp/core/BUILD b/cpp/core/BUILD index d4d5d83c..c4c69762 100644 --- a/cpp/core/BUILD +++ b/cpp/core/BUILD @@ -24,7 +24,7 @@ cc_library( compatible_with = ["//buildenv/target:non_prod"], copts = ["-DCORE_ADAPTER_DLL"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl/ios:__subpackages__", "//third_party/nearby_connections/windows:__subpackages__", ], deps = [ @@ -58,9 +58,9 @@ cc_library( compatible_with = ["//buildenv/target:non_prod"], copts = ["-DCORE_ADAPTER_DLL"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//analytics:__subpackages__", "//core:__subpackages__", + "//platform/impl/ios:__subpackages__", ], deps = [ "//absl/strings", diff --git a/cpp/platform/api/BUILD b/cpp/platform/api/BUILD index 61cf51cf..0b6678c9 100644 --- a/cpp/platform/api/BUILD +++ b/cpp/platform/api/BUILD @@ -37,7 +37,6 @@ cc_library( ], compatible_with = ["//buildenv/target:non_prod"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//platform/base:__pkg__", "//platform/impl:__subpackages__", "//platform/public:__pkg__", diff --git a/cpp/platform/base/BUILD b/cpp/platform/base/BUILD index a7cdf16c..270bbffe 100644 --- a/cpp/platform/base/BUILD +++ b/cpp/platform/base/BUILD @@ -44,7 +44,6 @@ cc_library( compatible_with = ["//buildenv/target:non_prod"], copts = ["-DCORE_ADAPTER_DLL"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//analytics:__subpackages__", "//third_party/nearby_connections/cpp/cal:__subpackages__", "//core:__subpackages__", diff --git a/cpp/platform/impl/ios/BUILD b/cpp/platform/impl/ios/BUILD new file mode 100644 index 00000000..fc79a3ca --- /dev/null +++ b/cpp/platform/impl/ios/BUILD @@ -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. +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.m", + "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 = [ + "//base", + "//absl/functional:bind_front", + "//core", + "//core:core_types", + "//platform/impl/ios/Source/Mediums", + "//platform/impl/ios/Source/Platform", + "//platform/impl/ios/Source/Shared", + "//third_party/objective_c/google_toolbox_for_mac:GTM_Logger", + ], +) diff --git a/cpp/platform/impl/ios/Source/GNCAdvertiser.h b/cpp/platform/impl/ios/Source/GNCAdvertiser.h new file mode 100644 index 00000000..03272bb6 --- /dev/null +++ b/cpp/platform/impl/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 + +#import "GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This contains info about a discoverer endpoint intitiating a connection with an advertiser. */ +@protocol GNCAdvertiserConnectionInfo +/** 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 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/cpp/platform/impl/ios/Source/GNCConnection.h b/cpp/platform/impl/ios/Source/GNCConnection.h new file mode 100644 index 00000000..85efca47 --- /dev/null +++ b/cpp/platform/impl/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 + +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 + +/** + * 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 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/cpp/platform/impl/ios/Source/GNCConnections.h b/cpp/platform/impl/ios/Source/GNCConnections.h new file mode 100644 index 00000000..71c280b0 --- /dev/null +++ b/cpp/platform/impl/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/cpp/platform/impl/ios/Source/GNCDiscoverer.h b/cpp/platform/impl/ios/Source/GNCDiscoverer.h new file mode 100644 index 00000000..12519c00 --- /dev/null +++ b/cpp/platform/impl/ios/Source/GNCDiscoverer.h @@ -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. + +#import + +#import "GNCConnection.h" + +NS_ASSUME_NONNULL_BEGIN + +/** This is info about an advertiser endpoint with which the discoverer has requested a connection. + */ +@protocol GNCDiscovererConnectionInfo +/** 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 connectionInfo, GNCConnectionResponseHandler responseHandler); + +/** + * This handler should be called to request a connection with an advertiser. + * + * @param name A human readable name of this endpoint, to be displayed on the other endpoint. + * @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)( + NSString *name, GNCDiscovererConnectionInitializationHandler connectionAuthorizationHandler, + GNCConnectionFailureHandler failureHandler); + +/** This contains info about a discovered advertiser endpoint. */ +@protocol GNCDiscoveredEndpointInfo +/** This is a human readable name of the advertiser. */ +@property(nonatomic, readonly, copy) NSString *name; +/** 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 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/cpp/platform/impl/ios/Source/GNCPayload.h b/cpp/platform/impl/ios/Source/GNCPayload.h new file mode 100644 index 00000000..fd3c034a --- /dev/null +++ b/cpp/platform/impl/ios/Source/GNCPayload.h @@ -0,0 +1,62 @@ +// 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 + +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; + +@end + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Internal/GNCAdvertiser.mm b/cpp/platform/impl/ios/Source/Internal/GNCAdvertiser.mm new file mode 100644 index 00000000..bb9e2d92 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Internal/GNCAdvertiser.mm @@ -0,0 +1,313 @@ +// 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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCAdvertiser.h" + +#include + +#include "third_party/absl/functional/bind_front.h" +#include "third_party/nearby_connections/cpp/core/core.h" +#include "third_party/nearby_connections/cpp/core/listeners.h" +#include "third_party/nearby_connections/cpp/core/options.h" +#include "third_party/nearby_connections/cpp/core/params.h" +#include "third_party/nearby_connections/cpp/core/status.h" +#include "third_party/nearby_connections/cpp/platform/base/byte_array.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCUtils.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/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::ConnectionOptions; +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 *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)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( + 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 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 payload_listener_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +using ::location::nearby::connections::GNCAdvertiserConnectionListener; + +@interface GNCAdvertiser () { + std::unique_ptr 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(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), + ConnectionOptions{ + .strategy = GNCStrategyToStrategy(strategy), + .auto_upgrade_bandwidth = true, + .enforce_topology_constraints = true, + }, + 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/cpp/platform/impl/ios/Source/Internal/GNCCore.h b/cpp/platform/impl/ios/Source/Internal/GNCCore.h new file mode 100644 index 00000000..d0837380 --- /dev/null +++ b/cpp/platform/impl/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 + +#include + +#include "core/core.h" +#include "core/internal/service_controller_router.h" +#include "platform/base/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/cpp/platform/impl/ios/Source/Internal/GNCCore.mm b/cpp/platform/impl/ios/Source/Internal/GNCCore.mm new file mode 100644 index 00000000..edbae4db --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" + +#include + +#include "third_party/absl/container/flat_hash_map.h" +#include "third_party/absl/container/internal/common.h" +#include "third_party/nearby_connections/cpp/core/core.h" +#include "third_party/nearby_connections/cpp/core/internal/service_controller_router.h" +#include "third_party/nearby_connections/cpp/platform/base/payload_id.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/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 _sending_urls; +} + +- (instancetype)init { + GTMLoggerInfo(@"GNCCore created"); + self = [super init]; + if (self) { + _service_controller_router = std::make_unique(); + _core = std::make_unique(_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/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h b/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h new file mode 100644 index 00000000..78884f18 --- /dev/null +++ b/cpp/platform/impl/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 + +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/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 +@property(nonatomic) GNCCore *core; +@property(nonatomic, copy) GNCEndpointId endpointId; +@property(nonatomic) dispatch_block_t deallocHandler; +@property(nonatomic) NSMutableDictionary *payloads; + ++ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId + core:(GNCCore *)core + deallocHandler:(dispatch_block_t)deallocHandler; +@end + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.mm b/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.mm new file mode 100644 index 00000000..40d6a4ed --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h" + +#include "third_party/nearby_connections/cpp/core/core.h" +#include "third_party/nearby_connections/cpp/core/payload.h" +#include "third_party/nearby_connections/cpp/platform/api/input_file.h" +#import "third_party/nearby_connections/cpp/platform/base/exception.h" +#include "third_party/nearby_connections/cpp/platform/base/input_stream.h" +#import "third_party/nearby_connections/cpp/platform/base/payload_id.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCPayload.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#include "third_party/nearby_connections/cpp/platform/public/file.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 Read() { return Read(kMaxChunkSize); } + + ExceptionOr 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(); + } else if (numberOfBytesRead < 0) { + // Stream error. + return ExceptionOr{Exception::kIo}; + } + return ExceptionOr(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::GenerateId(); + 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::GenerateId(); + // 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(1, CppStringFromObjCString(_endpointId)), + std::move(payload), ResultListener{}); + return progress; +} + +- (void)cancelPayloadWithId:(PayloadId)payloadId { + _core->_core->CancelPayload(payloadId, ResultListener{}); +} + +@end diff --git a/cpp/platform/impl/ios/Source/Internal/GNCDiscoverer.mm b/cpp/platform/impl/ios/Source/Internal/GNCDiscoverer.mm new file mode 100644 index 00000000..db0517fd --- /dev/null +++ b/cpp/platform/impl/ios/Source/Internal/GNCDiscoverer.mm @@ -0,0 +1,394 @@ +// 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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCDiscoverer.h" + +#include +#include + +#include "third_party/absl/functional/bind_front.h" +#include "third_party/nearby_connections/cpp/core/core.h" +#include "third_party/nearby_connections/cpp/core/listeners.h" +#include "third_party/nearby_connections/cpp/core/options.h" +#include "third_party/nearby_connections/cpp/core/status.h" +#include "third_party/nearby_connections/cpp/platform/base/byte_array.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCUtils.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/GTMLogger.h" + +NS_ASSUME_NONNULL_BEGIN + +using ::location::nearby::ByteArray; +using ::location::nearby::CppStringFromObjCString; +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 +@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 +@property(nonatomic, copy) NSString *name; +@end + +@implementation GNCDiscoveredEndpointInfo + +@synthesize requestConnection = _requestConnection; + ++ (instancetype)infoWithName:(NSString *)name + requestConnection:(GNCConnectionRequester)requestConnection { + GNCDiscoveredEndpointInfo *info = [[GNCDiscoveredEndpointInfo alloc] init]; + info.name = name; + 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 *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 *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( + 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 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 *endpoints_; + std::unique_ptr 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]; + + // 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(endpoint_info)); + GNCCore *core = discoverer.core; // don't capture |this| or |discoverer| + NSMapTable *endpoints = discoverer.endpoints; + GNCDiscoveredEndpointInfo *discEndpointInfo = [GNCDiscoveredEndpointInfo + infoWithName:name + requestConnection:^(NSString *name, + GNCDiscovererConnectionInitializationHandler connInitHandler, + GNCConnectionFailureHandler connFailureHandler) { + endpointInfo.connInitHandler = connInitHandler; + endpointInfo.connFailureHandler = connFailureHandler; + if (discoverer_connection_listener_ == nullptr) { + discoverer_connection_listener_ = + std::make_unique(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 = std::move(endpoint_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 discoverer_connection_listener_; +}; + +} // namespace connections +} // namespace nearby +} // namespace location + +using ::location::nearby::connections::GNCDiscoveryListener; + +@interface GNCDiscoverer () { + std::unique_ptr 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(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), + ConnectionOptions{ + .strategy = 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/cpp/platform/impl/ios/Source/Internal/GNCPayload+Internal.h b/cpp/platform/impl/ios/Source/Internal/GNCPayload+Internal.h new file mode 100644 index 00000000..3831230e --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Internal/GNCPayload.m b/cpp/platform/impl/ios/Source/Internal/GNCPayload.m new file mode 100644 index 00000000..b4ab2de8 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Internal/GNCPayload.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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCPayload.h" + +#include + +NS_ASSUME_NONNULL_BEGIN + +uint64_t GNCRandom64() { + return ((uint64_t)arc4random() << 32) + arc4random(); +} + +@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:GNCRandom64()]; +} + ++ (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:GNCRandom64()]; +} + ++ (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:GNCRandom64()]; +} + ++ (instancetype)payloadWithFileURL:(NSURL *)fileURL identifier:(int64_t)identifier { + return [[self alloc] initWithFileURL:fileURL identifier:identifier]; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h b/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h new file mode 100644 index 00000000..17357c3a --- /dev/null +++ b/cpp/platform/impl/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 + +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/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 *_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/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.mm b/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.mm new file mode 100644 index 00000000..1883d9ad --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCPayloadListener.h" + +#include + +#include "third_party/nearby_connections/cpp/core/core.h" +#include "third_party/nearby_connections/cpp/core/listeners.h" +#include "third_party/nearby_connections/cpp/core/payload.h" +#include "third_party/nearby_connections/cpp/platform/base/byte_array.h" +#include "third_party/nearby_connections/cpp/platform/base/exception.h" +#include "third_party/nearby_connections/cpp/platform/base/input_stream.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCPayload.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCoreConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCPayload+Internal.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#include "third_party/nearby_connections/cpp/platform/public/file.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 *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 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 *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/cpp/platform/impl/ios/Source/Internal/GNCUtils.h b/cpp/platform/impl/ios/Source/Internal/GNCUtils.h new file mode 100644 index 00000000..f0efbeef --- /dev/null +++ b/cpp/platform/impl/ios/Source/Internal/GNCUtils.h @@ -0,0 +1,43 @@ +// 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 + +#include + +#include "core/listeners.h" +#include "core/options.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCAdvertiser.h" +#import "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Internal/GNCUtils.mm b/cpp/platform/impl/ios/Source/Internal/GNCUtils.mm new file mode 100644 index 00000000..bec82bf8 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCUtils.h" + +#include "third_party/nearby_connections/cpp/core/strategy.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/GNCAdvertiser.h" +#import "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Internal/platform.mm b/cpp/platform/impl/ios/Source/Internal/platform.mm new file mode 100644 index 00000000..52198eef --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/api/platform.h" + +#include + +#include "third_party/nearby_connections/cpp/platform/api/mutex.h" +#include "third_party/nearby_connections/cpp/platform/base/payload_id.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Internal/GNCCore.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/atomic_boolean.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/atomic_uint32.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/condition_variable.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/count_down_latch.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/input_file.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/log_message.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/mutex.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/scheduled_executor.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/single_thread_executor.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/wifi_lan.h" +#include "third_party/nearby_connections/cpp/platform/impl/shared/file.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 ImplementationPlatform::CreateAtomicBoolean(bool initial_value) { + return std::make_unique(initial_value); +} + +std::unique_ptr ImplementationPlatform::CreateAtomicUint32( + std::uint32_t initial_value) { + return std::make_unique(initial_value); +} + +std::unique_ptr ImplementationPlatform::CreateCountDownLatch(std::int32_t count) { + return std::make_unique(count); +} + +std::unique_ptr 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(); + } else { + return absl::make_unique(); + } +} + +std::unique_ptr ImplementationPlatform::CreateConditionVariable(Mutex* mutex) { + return std::make_unique(static_cast(mutex)); +} + +std::unique_ptr 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(url); + } else { + return absl::make_unique(GetPayloadPath(payload_id), total_size); + } +} + +std::unique_ptr ImplementationPlatform::CreateOutputFile(PayloadId payload_id) { + return absl::make_unique(GetPayloadPath(payload_id)); +} + +std::unique_ptr ImplementationPlatform::CreateLogMessage( + const char* file, int line, LogMessage::Severity severity) { + return absl::make_unique(file, line, severity); +} + +// Java-like Executors +std::unique_ptr ImplementationPlatform::CreateSingleThreadExecutor() { + return std::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateMultiThreadExecutor( + int max_concurrency) { + return std::make_unique(max_concurrency); +} + +std::unique_ptr ImplementationPlatform::CreateScheduledExecutor() { + return std::make_unique(); +} + +// Mediums +std::unique_ptr ImplementationPlatform::CreateBluetoothAdapter() { + return nullptr; +} + +std::unique_ptr ImplementationPlatform::CreateBluetoothClassicMedium( + api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr ImplementationPlatform::CreateBleV2Medium( + api::BluetoothAdapter& adapter) { + return nullptr; +} + +std::unique_ptr ImplementationPlatform::CreateServerSyncMedium() { + return nullptr; +} + +std::unique_ptr ImplementationPlatform::CreateWifiMedium() { return nullptr; } + +std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { + return std::make_unique(); +} + +std::unique_ptr ImplementationPlatform::CreateWebRtcMedium() { return nullptr; } + +} // namespace api +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/ios/Source/Mediums/BUILD b/cpp/platform/impl/ios/Source/Mediums/BUILD new file mode 100644 index 00000000..19b86200 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Mediums/BUILD @@ -0,0 +1,57 @@ +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 = ["//platform/impl/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", + "//base", + "//absl/numeric:int128", + "//platform/impl/ios/Source/Shared", + "//third_party/objective_c/google_toolbox_for_mac:GTM_Logger", + ], +) + +objc_proto_library( + name = "ObjCProtos", + deps = [":Protos"], +) + +proto_library( + name = "Protos", + deps = [ + "//proto/connections:offline_wire_formats_proto", + ], +) diff --git a/cpp/platform/impl/ios/Source/Mediums/GNCLeaks.h b/cpp/platform/impl/ios/Source/Mediums/GNCLeaks.h new file mode 100644 index 00000000..1b044fd3 --- /dev/null +++ b/cpp/platform/impl/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 + +// Verifies that an object has been deallocated after the given time period. +void GNCVerifyDealloc(id object, NSTimeInterval timeInterval); diff --git a/cpp/platform/impl/ios/Source/Mediums/GNCLeaks.m b/cpp/platform/impl/ios/Source/Mediums/GNCLeaks.m new file mode 100644 index 00000000..10ff26bf --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h b/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h new file mode 100644 index 00000000..67d32fb1 --- /dev/null +++ b/cpp/platform/impl/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 + +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 + +/** + * 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 __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 *_Nullable TXTRecordData, + GNCMConnectionRequester requestConnection); + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.m b/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.m new file mode 100644 index 00000000..9e14f9f9 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h new file mode 100644 index 00000000..a8aef657 --- /dev/null +++ b/cpp/platform/impl/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 + +#import "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.m new file mode 100644 index 00000000..a4fffa6a --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h" + +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/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 () + +@property(nonatomic, copy) GNCMEndpointFoundHandler endpointFoundHandler; + +@property(nonatomic) NSNetServiceBrowser *netBrowser; +@property(nonatomic) NSMutableDictionary *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 *)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 *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 *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 *endpointIds = [_endpoints allKeysForObject:info]; + if (endpointIds.count > 0) { + // Get TXTRecord data. + NSData *data = [service TXTRecordData]; + NSDictionary *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 *)errorDict { + GTMLoggerDebug(@"Did not resolve service: %@", service); + NSArray *endpointIds = + [_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]]; + if (endpointIds.count > 0) { + _endpoints[endpointIds[0]].connectionHandler = nil; + } +} + +@end diff --git a/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h new file mode 100644 index 00000000..13706c48 --- /dev/null +++ b/cpp/platform/impl/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 + +#import "third_party/nearby_connections/cpp/platform/impl/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 +@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/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m new file mode 100644 index 00000000..01d6bd68 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.m @@ -0,0 +1,207 @@ +// 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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" + +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/GTMLogger.h" + +enum { kMaxPacketSize = 32 * 1024 }; + +@interface GNCMBonjourConnection () +@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; +@end + +@implementation GNCMBonjourConnection + +- (instancetype)initWithInputStream:(NSInputStream *)inputStream + outputStream:(NSOutputStream *)outputStream + queue:(nullable dispatch_queue_t)queue { + self = [super init]; + if (self) { + _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: + 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 (_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/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.h b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.h new file mode 100644 index 00000000..8c5522c2 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.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. + +#import +#import + +#import "third_party/nearby_connections/cpp/platform/impl/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 *)TXTRecordData + endpointConnectedHandler:(GNCMConnectionHandler)handler; + +@end + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.m b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.m new file mode 100644 index 00000000..73c07538 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.h" + +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/GTMLogger.h" + +@interface GNCMBonjourService () + +@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 *)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 *)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/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h new file mode 100644 index 00000000..e314dec6 --- /dev/null +++ b/cpp/platform/impl/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 + +// mDNS domain. +FOUNDATION_EXPORT NSString *_Nonnull const GNCMBonjourDomain; diff --git a/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m b/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.m new file mode 100644 index 00000000..d05af7e2 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourUtils.h" + +NSString *const GNCMBonjourDomain = @"local"; diff --git a/cpp/platform/impl/ios/Source/Platform/BUILD b/cpp/platform/impl/ios/Source/Platform/BUILD new file mode 100644 index 00000000..44886f36 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Platform/BUILD @@ -0,0 +1,93 @@ +# 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 = ["//platform/impl/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", + "//platform/api:platform", + "//platform/api:types", + "//platform/impl/ios/Source/Mediums", + "//platform/impl/ios/Source/Shared", + "//platform/impl/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 = [ + "//absl/strings:str_format", + "//absl/synchronization", + "//absl/time", + "//platform/api:platform", + "//platform/api:types", + ], +) + +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", + "//testing/base/public:gunit_main", + "//absl/synchronization", + "//absl/time", + "//thread/fiber", + ], +) diff --git a/cpp/platform/impl/ios/Source/Platform/atomic_boolean.h b/cpp/platform/impl/ios/Source/Platform/atomic_boolean.h new file mode 100644 index 00000000..f0e40b0f --- /dev/null +++ b/cpp/platform/impl/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 + +#include "platform/api/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/cpp/platform/impl/ios/Source/Platform/atomic_boolean_test.cc b/cpp/platform/impl/ios/Source/Platform/atomic_boolean_test.cc new file mode 100644 index 00000000..52638fb9 --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/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/cpp/platform/impl/ios/Source/Platform/atomic_uint32.h b/cpp/platform/impl/ios/Source/Platform/atomic_uint32.h new file mode 100644 index 00000000..7e0b736f --- /dev/null +++ b/cpp/platform/impl/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 +#include + +#include "platform/api/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 value_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_ diff --git a/cpp/platform/impl/ios/Source/Platform/atomic_uint32_test.cc b/cpp/platform/impl/ios/Source/Platform/atomic_uint32_test.cc new file mode 100644 index 00000000..6d5980de --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/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/cpp/platform/impl/ios/Source/Platform/condition_variable.cc b/cpp/platform/impl/ios/Source/Platform/condition_variable.cc new file mode 100644 index 00000000..603e1772 --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/ios/Source/Platform/condition_variable.h" + +#include "platform/impl/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/cpp/platform/impl/ios/Source/Platform/condition_variable.h b/cpp/platform/impl/ios/Source/Platform/condition_variable.h new file mode 100644 index 00000000..b7cb0f89 --- /dev/null +++ b/cpp/platform/impl/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 "platform/api/condition_variable.h" +#include "platform/impl/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/cpp/platform/impl/ios/Source/Platform/condition_variable_test.cc b/cpp/platform/impl/ios/Source/Platform/condition_variable_test.cc new file mode 100644 index 00000000..a1ebe90d --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/ios/Source/Platform/condition_variable.h" + +#include "gtest/gtest.h" +#include "absl/time/clock.h" +#include "platform/impl/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/cpp/platform/impl/ios/Source/Platform/count_down_latch.cc b/cpp/platform/impl/ios/Source/Platform/count_down_latch.cc new file mode 100644 index 00000000..108829a5 --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/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 CountDownLatch::Await(absl::Duration timeout) { + bool condition = mutex_.LockWhenWithTimeout( + absl::Condition(IsZeroOrNegative, &count_), timeout); + mutex_.Unlock(); + return ExceptionOr(condition); +} + +void CountDownLatch::CountDown() { + absl::MutexLock lock(&mutex_); + count_--; +} + +} // namespace ios +} // namespace nearby +} // namespace location diff --git a/cpp/platform/impl/ios/Source/Platform/count_down_latch.h b/cpp/platform/impl/ios/Source/Platform/count_down_latch.h new file mode 100644 index 00000000..e8ba79c6 --- /dev/null +++ b/cpp/platform/impl/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 "platform/api/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 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/cpp/platform/impl/ios/Source/Platform/count_down_latch_test.cc b/cpp/platform/impl/ios/Source/Platform/count_down_latch_test.cc new file mode 100644 index 00000000..6a354cec --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/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/cpp/platform/impl/ios/Source/Platform/crypto.mm b/cpp/platform/impl/ios/Source/Platform/crypto.mm new file mode 100644 index 00000000..cc2dc344 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/api/crypto.h" + +#import "third_party/absl/strings/string_view.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#import "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Platform/input_file.h b/cpp/platform/impl/ios/Source/Platform/input_file.h new file mode 100644 index 00000000..c96f69fd --- /dev/null +++ b/cpp/platform/impl/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 + +#include "platform/api/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 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/cpp/platform/impl/ios/Source/Platform/input_file.mm b/cpp/platform/impl/ios/Source/Platform/input_file.mm new file mode 100644 index 00000000..b8addef8 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/input_file.h" + +#include + +#import "third_party/nearby_connections/cpp/platform/base/exception.h" +#import "third_party/nearby_connections/cpp/platform/impl/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 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(); + } else if (numberOfBytesRead < 0) { + // Stream error. + return ExceptionOr(Exception::kIo); + } + return ExceptionOr(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/cpp/platform/impl/ios/Source/Platform/log_message.h b/cpp/platform/impl/ios/Source/Platform/log_message.h new file mode 100644 index 00000000..ce739c71 --- /dev/null +++ b/cpp/platform/impl/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 "base/check.h" +#include "platform/api/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: + absl::LogStreamer 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/cpp/platform/impl/ios/Source/Platform/log_message.mm b/cpp/platform/impl/ios/Source/Platform/log_message.mm new file mode 100644 index 00000000..bb50868a --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/log_message.h" + +#include "base/logging.h" +#include "third_party/nearby_connections/cpp/platform/api/log_message.h" +#include "third_party/objective_c/google_toolbox_for_mac/Foundation/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/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.h b/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.h new file mode 100644 index 00000000..e44e5b3e --- /dev/null +++ b/cpp/platform/impl/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 "platform/api/submittable_executor.h" +#import "third_party/nearby_connections/cpp/platform/base/runnable.h" +#import "third_party/nearby_connections/cpp/platform/impl/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 scheduled_executor_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_ diff --git a/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.mm b/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.mm new file mode 100644 index 00000000..5070e4e5 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/multi_thread_executor.h" + +#include "third_party/nearby_connections/cpp/platform/base/runnable.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/scheduled_executor.h" + +namespace location { +namespace nearby { +namespace ios { + +MultiThreadExecutor::MultiThreadExecutor(int max_concurrency) { + scheduled_executor_ = std::make_unique(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/cpp/platform/impl/ios/Source/Platform/mutex.h b/cpp/platform/impl/ios/Source/Platform/mutex.h new file mode 100644 index 00000000..4ec15818 --- /dev/null +++ b/cpp/platform/impl/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 "platform/api/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(&per_thread); + } + + std::atomic thread_id_{0}; + int count_{0}; + absl::Mutex mutex_; +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_MUTEX_H_ diff --git a/cpp/platform/impl/ios/Source/Platform/mutex_test.cc b/cpp/platform/impl/ios/Source/Platform/mutex_test.cc new file mode 100644 index 00000000..521cfda3 --- /dev/null +++ b/cpp/platform/impl/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 "platform/impl/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/cpp/platform/impl/ios/Source/Platform/scheduled_executor.h b/cpp/platform/impl/ios/Source/Platform/scheduled_executor.h new file mode 100644 index 00000000..2e7b773c --- /dev/null +++ b/cpp/platform/impl/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 + +#include +#include + +#include "platform/api/scheduled_executor.h" +#include "platform/base/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 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/cpp/platform/impl/ios/Source/Platform/scheduled_executor.mm b/cpp/platform/impl/ios/Source/Platform/scheduled_executor.mm new file mode 100644 index 00000000..1713adea --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/scheduled_executor.h" + +#import + +#include "third_party/absl/time/time.h" +#include "third_party/nearby_connections/cpp/platform/base/runnable.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/atomic_boolean.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 _canceled; +} +@end + +@implementation GNCRunnableWrapper + ++ (instancetype)wrapperWithRunnable:(location::nearby::Runnable)runnable { + GNCRunnableWrapper *wrapper = [[GNCRunnableWrapper alloc] init]; + wrapper->_runnable = runnable; + wrapper->_canceled = std::make_unique(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 ScheduledExecutor::Schedule(Runnable &&runnable, + absl::Duration duration) { + if (impl_.shuttingDown) return std::shared_ptr(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(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/cpp/platform/impl/ios/Source/Platform/single_thread_executor.h b/cpp/platform/impl/ios/Source/Platform/single_thread_executor.h new file mode 100644 index 00000000..eafb254a --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/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/cpp/platform/impl/ios/Source/Platform/system_clock.cc b/cpp/platform/impl/ios/Source/Platform/system_clock.cc new file mode 100644 index 00000000..c84d4a73 --- /dev/null +++ b/cpp/platform/impl/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 "platform/api/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/cpp/platform/impl/ios/Source/Platform/utils.h b/cpp/platform/impl/ios/Source/Platform/utils.h new file mode 100644 index 00000000..88c883b0 --- /dev/null +++ b/cpp/platform/impl/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 +#import + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "platform/base/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 *CBUUIDSetFromBluetoothUUIDStringSet(const std::set &bluetoothUUIDSet); + +/// Converts an NSSet of CBUUID to a C++ set of strings (representing Bluetooth UUIDs). +std::set BluetoothUUIDStringSetFromCBUUIDSet(NSSet *bluetoothUUIDSet); + +/// Converts a C++ TxtRecord to an Obj-C TxtRecord. +NSDictionary *NSDictionaryFromCppTxtRecords( + const absl::flat_hash_map &txt_records); + +/// Converts an Obj-C TxtRecord to a C++ TxtRecord. +absl::flat_hash_map AbslHashMapFromObjCTxtRecords( + NSDictionary *txtRecords); + +} // namespace nearby +} // namespace location + +NS_ASSUME_NONNULL_END diff --git a/cpp/platform/impl/ios/Source/Platform/utils.mm b/cpp/platform/impl/ios/Source/Platform/utils.mm new file mode 100644 index 00000000..4a98fb74 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" + +#import "third_party/absl/container/flat_hash_map.h" +#include "third_party/absl/strings/string_view.h" +#include "third_party/nearby_connections/cpp/platform/base/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* CBUUIDSetFromBluetoothUUIDStringSet(const std::set& bluetoothUUIDSet) { + NSMutableSet* objcBluetoothUUIDSet = [NSMutableSet set]; + for (std::set::const_iterator it = bluetoothUUIDSet.begin(); + it != bluetoothUUIDSet.end(); ++it) { + [objcBluetoothUUIDSet addObject:CBUUIDFromBluetoothUUIDString(*it)]; + } + return objcBluetoothUUIDSet; +} + +std::set BluetoothUUIDStringSetFromCBUUIDSet(NSSet* bluetoothUUIDSet) { + std::set cppBluetoothUUIDSet; + for (CBUUID* bluetoothUUID in bluetoothUUIDSet) { + cppBluetoothUUIDSet.insert(BluetoothUUIDStringFromCBUUID(bluetoothUUID)); + } + return cppBluetoothUUIDSet; +} + +NSDictionary* NSDictionaryFromCppTxtRecords( + const absl::flat_hash_map& txt_records) { + NSMutableArray* keyArray = [[NSMutableArray alloc] init]; + NSMutableArray* 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* dict = [[NSDictionary alloc] initWithObjects:valueArray + forKeys:keyArray]; + return dict; +} + +absl::flat_hash_map AbslHashMapFromObjCTxtRecords( + NSDictionary* txtRecords) { + absl::flat_hash_map 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/cpp/platform/impl/ios/Source/Platform/wifi_lan.h b/cpp/platform/impl/ios/Source/Platform/wifi_lan.h new file mode 100644 index 00000000..de4416cd --- /dev/null +++ b/cpp/platform/impl/ios/Source/Platform/wifi_lan.h @@ -0,0 +1,198 @@ +// 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 +#include + +#include "absl/base/thread_annotations.h" +#include "absl/container/flat_hash_map.h" +#include "platform/api/wifi_lan.h" +#include "platform/base/nsd_service_info.h" +#import "third_party/nearby_connections/cpp/platform/impl/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 Read(std::int64_t size) override; + Exception Close() override; + + GNCMConnectionHandlers* GetConnectionHandlers() { return connectionHandlers_; } + + private: + GNCMConnectionHandlers* connectionHandlers_; + NSMutableArray* newDataPackets_; + NSMutableData* accumulatedData_; + NSCondition* condition_; +}; + +/** OutputStream that writes to GNCMConnection. */ +class WifiLanOutputStream : public OutputStream { + public: + explicit WifiLanOutputStream(id connection) + : connection_(connection), condition_([[NSCondition alloc] init]) {} + ~WifiLanOutputStream() override; + + Exception Write(const ByteArray& data) override; + Exception Flush() override; + Exception Close() override; + + private: + id connection_; + NSCondition* condition_; +}; + +/** Concrete WifiLanSocket implementation. */ +class WifiLanSocket : public api::WifiLanSocket { + public: + WifiLanSocket() = default; + explicit WifiLanSocket(id 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 input_stream_; + std::unique_ptr 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 Accept() override ABSL_LOCKS_EXCLUDED(mutex_); + Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_); + + bool Connect(std::unique_ptr socket) ABSL_LOCKS_EXCLUDED(mutex_); + void SetCloseNotifier(std::function 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> pending_sockets_ ABSL_GUARDED_BY(mutex_); + std::function 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 ConnectToService( + const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) override; + std::unique_ptr ConnectToService( + const std::string& ip_address, int port, CancellationFlag* cancellation_flag) override; + std::unique_ptr ListenForService(int port) override + ABSL_LOCKS_EXCLUDED(mutex_); + + 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 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 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 server_sockets_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map connection_requesters_ + ABSL_GUARDED_BY(mutex_); +}; + +} // namespace ios +} // namespace nearby +} // namespace location + +#endif // PLATFORM_IMPL_IOS_WIFI_LAN_H_ diff --git a/cpp/platform/impl/ios/Source/Platform/wifi_lan.mm b/cpp/platform/impl/ios/Source/Platform/wifi_lan.mm new file mode 100644 index 00000000..476e2677 --- /dev/null +++ b/cpp/platform/impl/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 "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/wifi_lan.h" + +#include +#include +#include + +#include "third_party/absl/container/flat_hash_map.h" +#include "third_party/absl/container/internal/common.h" +#include "third_party/absl/memory/memory.h" +#include "third_party/absl/strings/str_cat.h" +#include "third_party/absl/strings/str_format.h" +#include "third_party/absl/synchronization/mutex.h" +#include "third_party/nearby_connections/cpp/platform/api/wifi_lan.h" +#include "third_party/nearby_connections/cpp/platform/base/cancellation_flag.h" +#include "third_party/nearby_connections/cpp/platform/base/exception.h" +#include "third_party/nearby_connections/cpp/platform/base/nsd_service_info.h" +#include "third_party/nearby_connections/cpp/platform/base/prng.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/GNCMConnection.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourBrowser.h" +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Mediums/WifiLan/GNCMBonjourService.h" +#include "third_party/nearby_connections/cpp/platform/impl/ios/Source/Platform/utils.h" +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/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 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(ByteArrayFromNSData(dataToReturn)); + } else { + return ExceptionOr{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 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 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 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 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* 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 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(connection); + GNCMConnectionHandlers* connectionHandlers = + static_cast(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* _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 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 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 connection) { + // If the connection wasn't successfully established, return a NULL socket. + if (connection) { + socket = absl::make_unique(connection); + } + + dispatch_group_leave(group); // unblock + return socket != nullptr ? static_cast(socket->GetInputStream()) + .GetConnectionHandlers() + : nullptr; + }); + } + dispatch_group_wait(group, DISPATCH_TIME_FOREVER); + + return std::move(socket); +} + +std::unique_ptr WifiLanMedium::ConnectToService( + const std::string& ip_address, int port, CancellationFlag* cancellation_flag) { + // Not implemented. + return {}; +} + +std::unique_ptr WifiLanMedium::ListenForService(int port) { + auto server_socket = std::make_unique(); + // 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(raw_ip_addr >> 24); + ip_address[1] = static_cast(raw_ip_addr >> 16); + ip_address[2] = static_cast(raw_ip_addr >> 8); + ip_address[3] = static_cast(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/cpp/platform/impl/ios/Source/Shared/BUILD b/cpp/platform/impl/ios/Source/Shared/BUILD new file mode 100644 index 00000000..a4ac1dcf --- /dev/null +++ b/cpp/platform/impl/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 = ["//platform/impl/ios:__subpackages__"]) + +objc_library( + name = "Shared", + srcs = [ + "GNCUtils.m", + ], + hdrs = [ + "GNCUtils.h", + ], + deps = [ + "//third_party/objective_c/google_toolbox_for_mac:GTM_StringEncoding", + "//smhasher:libmurmur3", + ], +) diff --git a/cpp/platform/impl/ios/Source/Shared/GNCUtils.h b/cpp/platform/impl/ios/Source/Shared/GNCUtils.h new file mode 100644 index 00000000..0d27c550 --- /dev/null +++ b/cpp/platform/impl/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 + +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/cpp/platform/impl/ios/Source/Shared/GNCUtils.m b/cpp/platform/impl/ios/Source/Shared/GNCUtils.m new file mode 100644 index 00000000..cad4a664 --- /dev/null +++ b/cpp/platform/impl/ios/Source/Shared/GNCUtils.m @@ -0,0 +1,65 @@ +#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Shared/GNCUtils.h" + +#import + +#import "third_party/objective_c/google_toolbox_for_mac/Foundation/GTMStringEncoding.h" +// 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. + + +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/cpp/platform/impl/ios/Tests/BUILD b/cpp/platform/impl/ios/Tests/BUILD new file mode 100644 index 00000000..7084091c --- /dev/null +++ b/cpp/platform/impl/ios/Tests/BUILD @@ -0,0 +1,64 @@ +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 = [ + "//base", + "//platform/impl/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 = [ + "//platform/impl/ios/Source/Platform", + "//platform/impl/ios/Source/Shared", + ], +) + +ios_unit_test( + name = "SharedTests", + minimum_os_version = "12.0", + runner = "//testing/utp/ios:IOS_12", + deps = [ + ":SharedTestslib", + ], +) diff --git a/cpp/platform/impl/ios/Tests/Platform/GNCCryptoTest.mm b/cpp/platform/impl/ios/Tests/Platform/GNCCryptoTest.mm new file mode 100644 index 00000000..884be705 --- /dev/null +++ b/cpp/platform/impl/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 + +#include "third_party/nearby_connections/cpp/platform/api/crypto.h" +#include "third_party/nearby_connections/cpp/platform/base/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/cpp/platform/impl/ios/Tests/Platform/GNCInputFileTest.mm b/cpp/platform/impl/ios/Tests/Platform/GNCInputFileTest.mm new file mode 100644 index 00000000..4e038ae8 --- /dev/null +++ b/cpp/platform/impl/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 + +#import "third_party/nearby_connections/cpp/platform/base/byte_array.h" +#import "third_party/nearby_connections/cpp/platform/base/exception.h" +#import "third_party/nearby_connections/cpp/platform/impl/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(testURL); + XCTAssert(input_file != nullptr); + + XCTAssertEqual(input_file->GetTotalSize(), 0); + ExceptionOr 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(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/cpp/platform/impl/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm b/cpp/platform/impl/ios/Tests/Platform/GNCMultiThreadExecutorTest.mm new file mode 100644 index 00000000..c9948c55 --- /dev/null +++ b/cpp/platform/impl/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 + +#include "third_party/absl/time/time.h" +#include "third_party/nearby_connections/cpp/platform/api/cancelable.h" +#include "third_party/nearby_connections/cpp/platform/api/executor.h" +#include "third_party/nearby_connections/cpp/platform/api/platform.h" +#include "third_party/nearby_connections/cpp/platform/api/submittable_executor.h" +#include "third_party/nearby_connections/cpp/platform/base/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)executor { + std::unique_ptr executor = + ImplementationPlatform::CreateMultiThreadExecutor(4); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor executes runnables as expected. +- (void)testExecute { + std::unique_ptr 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 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 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 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/cpp/platform/impl/ios/Tests/Platform/GNCScheduledExecutorTest.mm b/cpp/platform/impl/ios/Tests/Platform/GNCScheduledExecutorTest.mm new file mode 100644 index 00000000..278c6ee7 --- /dev/null +++ b/cpp/platform/impl/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 + +#include "third_party/absl/time/time.h" +#include "third_party/nearby_connections/cpp/platform/api/cancelable.h" +#include "third_party/nearby_connections/cpp/platform/api/executor.h" +#include "third_party/nearby_connections/cpp/platform/api/platform.h" +#include "third_party/nearby_connections/cpp/platform/api/scheduled_executor.h" +#include "third_party/nearby_connections/cpp/platform/base/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)executor { + std::unique_ptr executor = ImplementationPlatform::CreateScheduledExecutor(); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor schedules operation at the specified time. +- (void)testScheduling { + std::unique_ptr 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 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 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 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 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/cpp/platform/impl/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm b/cpp/platform/impl/ios/Tests/Platform/GNCSingleThreadExecutorTest.mm new file mode 100644 index 00000000..85f18683 --- /dev/null +++ b/cpp/platform/impl/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 + +#include "third_party/absl/time/time.h" +#include "third_party/nearby_connections/cpp/platform/api/cancelable.h" +#include "third_party/nearby_connections/cpp/platform/api/executor.h" +#include "third_party/nearby_connections/cpp/platform/api/platform.h" +#include "third_party/nearby_connections/cpp/platform/api/submittable_executor.h" +#include "third_party/nearby_connections/cpp/platform/base/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)executor { + std::unique_ptr executor = + ImplementationPlatform::CreateSingleThreadExecutor(); + XCTAssert(executor != nullptr); + return executor; +} + +// Tests that the executor executes runnables as expected. +- (void)testRunnables { + std::unique_ptr 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 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 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/cpp/platform/impl/ios/Tests/Shared/GNCUtilsTest.mm b/cpp/platform/impl/ios/Tests/Shared/GNCUtilsTest.mm new file mode 100644 index 00000000..cbb37df2 --- /dev/null +++ b/cpp/platform/impl/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 + +#include "third_party/nearby_connections/cpp/platform/base/byte_array.h" +#include "third_party/nearby_connections/cpp/platform/impl/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 diff --git a/cpp/platform/impl/shared/BUILD b/cpp/platform/impl/shared/BUILD index 71e588dc..ffb820e3 100644 --- a/cpp/platform/impl/shared/BUILD +++ b/cpp/platform/impl/shared/BUILD @@ -49,7 +49,6 @@ cc_library( hdrs = ["file.h"], compatible_with = ["//buildenv/target:non_prod"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//platform/impl:__subpackages__", ], deps = [ diff --git a/cpp/platform/public/BUILD b/cpp/platform/public/BUILD index d1e122de..086645b1 100644 --- a/cpp/platform/public/BUILD +++ b/cpp/platform/public/BUILD @@ -52,10 +52,10 @@ cc_library( ], compatible_with = ["//buildenv/target:non_prod"], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", "//analytics:__subpackages__", "//core:__subpackages__", "//platform/base:__pkg__", + "//platform/impl/ios:__subpackages__", "//platform/impl/windows:__subpackages__", "//platform/public:__pkg__", ], diff --git a/proto/connections/BUILD b/proto/connections/BUILD index 78b02719..87b14c9b 100644 --- a/proto/connections/BUILD +++ b/proto/connections/BUILD @@ -26,7 +26,7 @@ proto_library( "//buildenv/target:non_prod", ], visibility = [ - "//googlemac/iPhone/Shared/Nearby/Connections:__subpackages__", + "//platform/impl/ios:__subpackages__", ], )