ios: nearbyConnections: Move source to //third_party.

PiperOrigin-RevId: 408046875
This commit is contained in:
edwinwu
2021-11-06 09:45:54 -07:00
committed by Copybara-Service
parent 8c2dd35eac
commit fb0337ebfa
76 changed files with 6192 additions and 7 deletions
-1
View File
@@ -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__",
-1
View File
@@ -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__",
+52
View File
@@ -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",
],
)
@@ -0,0 +1,84 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
/** This contains info about a discoverer endpoint intitiating a connection with an advertiser. */
@protocol GNCAdvertiserConnectionInfo <NSObject>
/** This is a human readable name of the discoverer. */
@property(nonatomic, readonly, copy) NSString *name;
/** This token can be used to verify the identity of the discoverer. */
@property(nonatomic, readonly, copy) NSString *authToken;
@end
/** This class contains success and failure handlers for the connection request. */
@interface GNCConnectionResultHandlers : NSObject
/**
* This factory method creates a pair of handlers for a successful or failed connection.
*
* @param successHandler This handler is called if both endpoints accept the connection.
* A @c GNCConnection object is passed, meaning that the connection has
* been established and you may start sending and receiving payloads.
* @param failureHandler This handler is called if either endpoint rejects the connection.
*/
+ (instancetype)successHandler:(GNCConnectionHandler)successHandler
failureHandler:(GNCConnectionFailureHandler)failureHandler;
@end
/**
* This handler is called when a discoverer requests a connection with an advertiser. In
* response, the advertiser should accept or reject via @c responseHandler.
*
* @param endpointId The ID of the endpoint.
* @param connectionInfo Information about the discoverer.
* @param responseHandler Handler for the connection response, which is either an acceptance or
* rejection of the connection request.
* @return Handlers for the final connection result. This will be called as soon as the final
* connection result is known, when either side rejects or both sides accept.
*/
typedef GNCConnectionResultHandlers *_Nonnull (^GNCAdvertiserConnectionInitiationHandler)(
GNCEndpointId endpointId, id<GNCAdvertiserConnectionInfo> connectionInfo,
GNCConnectionResponseHandler responseHandler);
/**
* An advertiser broadcasts a service that can be seen by discoverers, which can then make
* requests to connect to it. Release the advertiser object to stop advertising.
*/
@interface GNCAdvertiser : NSObject
/**
* Factory method that creates an advertiser.
*
* @param endpointInfo A data for endpoint info which contains readable name of this endpoint,
* to be displayed on other endpoints.
* @param serviceId A string that uniquely identifies the advertised service.
* @param strategy The connection topology to use.
* @param connectionInitiationHandler A handler that is called when a discoverer requests a
* connection with this endpoint.
*/
+ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo
serviceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
connectionInitiationHandler:
(GNCAdvertiserConnectionInitiationHandler)connectionInitiationHandler;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,167 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@class GNCBytesPayload, GNCStreamPayload, GNCFilePayload;
/** Response to a connection request. */
typedef NS_ENUM(NSInteger, GNCConnectionResponse) {
GNCConnectionResponseReject, // reject the connection request
GNCConnectionResponseAccept, // accept the connection request
};
/** Reason for a failed connection request. */
typedef NS_ENUM(NSInteger, GNCConnectionFailure) {
GNCConnectionFailureRejected, // an endpoint rejected the connection request
GNCConnectionFailureUnknown, // there was an error while attempting to make the connection
};
/** Handler for a @c GNCConnectionFailure value. */
typedef void (^GNCConnectionFailureHandler)(GNCConnectionFailure);
/** Reasons that a connection can be severed by either endpoint. */
typedef NS_ENUM(NSInteger, GNCDisconnectedReason) {
GNCDisconnectedReasonUnknown, // the endpoint can no longer be reached
};
/** Handler for a @c GNCDisconnectedReason value. */
typedef void (^GNCDisconnectedHandler)(GNCDisconnectedReason);
/** Result of a payload transfer. */
typedef NS_ENUM(NSInteger, GNCPayloadResult) {
GNCPayloadResultSuccess, // Payload delivery was successful.
GNCPayloadResultFailure, // An error occurred during payload delivery.
GNCPayloadResultCanceled, // Payload delivery was canceled.
};
/** Handler for a @c GNCPayloadResult value. */
typedef void (^GNCPayloadResultHandler)(GNCPayloadResult);
/** Connection topology. See https://developers.google.com/nearby/connections/strategies. */
typedef NS_ENUM(NSInteger, GNCStrategy) {
GNCStrategyCluster, // M-to-N
GNCStrategyStar, // 1-to-N
GNCStrategyPointToPoint, // 1-to-1
};
/** Every endpoint has a unique identifier. */
typedef NSString *GNCEndpointId;
/** This handler receives a Bytes payload. It is called when the payload data is fully received. */
typedef void (^GNCBytesPayloadHandler)(GNCBytesPayload *payload);
/**
* This handler receives a Stream payload, signifying the start of receipt of a stream. The payload
* data should be read from the supplied input stream. The progress object can be used to monitor
* progress or cancel the operation. This handler must return a completion handler, which is
* called when the operation is finished.
*/
typedef GNCPayloadResultHandler _Nonnull (^GNCStreamPayloadHandler)(GNCStreamPayload *payload,
NSProgress *progress);
/**
* This handler receives a File payload, signifying the start of receipt of a file. The
* progress object can be used to monitor progress or cancel the operation. This handler must
* return a completion handler, which is called when the operation finishes successfully or if
* there is an error. The file will be stored in a temporary location. If an error occurs or the
* operation is canceled, the file will contain all data that was received. It is the client's
* responsibility to delete the file when it is no longer needed.
*/
typedef GNCPayloadResultHandler _Nonnull (^GNCFilePayloadHandler)(GNCFilePayload *payload,
NSProgress *progress);
/** This class contains optional handlers for a connection. */
@interface GNCConnectionHandlers : NSObject
/**
* This handler receives Bytes payloads. It is optional; apps that don't send and receive Bytes
* payloads need not supply this handler.
*/
@property(nonatomic, nullable) GNCBytesPayloadHandler bytesPayloadHandler;
/**
* This handler receives a stream that delivers a payload in chunks. It is optional; apps that
* don't send and receive Stream payloads need not supply this handler.
*/
@property(nonatomic, nullable) GNCStreamPayloadHandler streamPayloadHandler;
/**
* This handler receives a File payload. It is optional; apps that don't send and receive File
* payloads need not supply this handler.
* Note: File payloads are not yet supported.
*/
@property(nonatomic, nullable) GNCFilePayloadHandler filePayloadHandler;
/**
* This handler is called when the connection is ended, whether due to the endpoint disconnecting
* or moving out of range. It is optional.
*/
@property(nonatomic, nullable) GNCDisconnectedHandler disconnectedHandler;
/**
* This factory method lets you specify a subset of the connection handlers in a single expression.
*
* @param builderBlock Set up the handlers in this block.
*/
+ (instancetype)handlersWithBuilder:(void (^)(GNCConnectionHandlers *))builderBlock;
@end
/**
* This represents a connection with an endpoint. Use it to send payloads to the endpoint, and
* release it to disconnect.
*/
@protocol GNCConnection <NSObject>
/**
* Send a Bytes payload. A progress object is returned, which can be used to monitor
* progress or cancel the operation. |completion| will be called when the operation completes
* (in all cases, even if failed or was canceled).
*/
- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload
completion:(GNCPayloadResultHandler)completion;
/**
* Send a Stream payload. A progress object is returned, which can be used to monitor
* progress or cancel the operation. The stream data is read from the supplied NSInputStream.
* |completion| will be called when the operation completes.
*/
- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload
completion:(GNCPayloadResultHandler)completion;
/**
* Send a File payload. A progress object is returned, which can be used to monitor progress or
* cancel the operation. |completion| will be called when the operation completes.
* Note: File payloads are not yet supported.
*/
- (NSProgress *)sendFilePayload:(GNCFilePayload *)payload
completion:(GNCPayloadResultHandler)completion;
@end
/**
* This handler takes a @c GNCConnection object and returns a @c GNCConnectionHandlers
* object containing the desired payload and connection-ended handlers.
*/
typedef GNCConnectionHandlers *_Nonnull (^GNCConnectionHandler)(id<GNCConnection> connection);
/**
* This handler takes a response to a connection request. Pass @c GNCConnectionResponseAccept to
* accept the request and @c GNCConnectionResponseReject to reject it.
*/
typedef void (^GNCConnectionResponseHandler)(GNCConnectionResponse response);
NS_ASSUME_NONNULL_END
@@ -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"
@@ -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 <Foundation/Foundation.h>
#import "GNCConnection.h"
NS_ASSUME_NONNULL_BEGIN
/** This is info about an advertiser endpoint with which the discoverer has requested a connection.
*/
@protocol GNCDiscovererConnectionInfo <NSObject>
/** This token can be used to verify the identity of the advertiser. */
@property(nonatomic, readonly, copy) NSString *authToken;
@end
/**
* This handler is called to establish authorization with the advertiser. In response,
* @c responseHandler should be called to accept or reject the connection.
*
* @param connectionInfo Information about the advertiser.
* @param responseHandler Handler for the connection response, which is either an acceptance or
* rejection of the connection request.
* @return Handler for the connection if it was successful.
*/
typedef GNCConnectionHandler _Nonnull (^GNCDiscovererConnectionInitializationHandler)(
id<GNCDiscovererConnectionInfo> connectionInfo, GNCConnectionResponseHandler responseHandler);
/**
* This handler should be called to request a connection with an advertiser.
*
* @param 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 <NSObject>
/** 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<GNCDiscoveredEndpointInfo> endpointInfo);
/**
* A discoverer searches for endpoints advertising the specified service, and allows connection
* requests to be sent to them. Release the discoverer object to stop discovering.
*/
@interface GNCDiscoverer : NSObject
/**
* Factory method that creates a discoverer.
*
* @param serviceId A string that uniquely identifies the advertised service to search for.
* @param strategy The connection topology to use.
* @param endpointFoundHandler This handler is called when an endpoint advertising the service is
* discovered.
*/
+ (instancetype)discovererWithServiceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler;
@end
NS_ASSUME_NONNULL_END
+62
View File
@@ -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 <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** This class encapsulates a Bytes payload. */
@interface GNCBytesPayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** The content of the payload. */
@property(nonatomic, readonly) NSData *bytes;
/**
* Creates a Bytes payload object.
* Note: To maximize performance, @c bytes is strongly referenced, not copied.
*/
+ (instancetype)payloadWithBytes:(NSData *)bytes;
@end
/** This class encapsulates a Stream payload. */
@interface GNCStreamPayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** The payload data is read from this input stream. */
@property(nonatomic, readonly) NSInputStream *stream;
+ (instancetype)payloadWithStream:(NSInputStream *)stream;
@end
/** This class encapsulates a File payload. */
@interface GNCFilePayload : NSObject
/** The unique identifier of the payload. */
@property(nonatomic, readonly) int64_t identifier;
/** A URL that identifies the file. */
@property(nonatomic, readonly, copy) NSURL *fileURL;
+ (instancetype)payloadWithFileURL:(NSURL *)fileURL;
@end
NS_ASSUME_NONNULL_END
@@ -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 <string>
#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<GNCEndpointId, GNCAdvertiserEndpointInfo *> *endpoints;
@end
/** C++ classes passed to the core library by GNCAdvertiser. */
namespace location {
namespace nearby {
namespace connections {
/** This class contains the callbacks for establishing and severing a connection. */
class GNCAdvertiserConnectionListener {
public:
explicit GNCAdvertiserConnectionListener(GNCAdvertiser *advertiser) : advertiser_(advertiser) {}
void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (endpointInfo) {
GTMLoggerError(@"Connection already initiated for endpoint: %@", endpointId);
} else {
// TODO(b/169292092): endpointInfo is an advertisement byte array. Need to implement to
// extract the endpoint name not just force to cast string.
NSString *name = ObjCStringFromCppString(std::string(info.remote_endpoint_info));
NSString *authToken = ObjCStringFromCppString(info.authentication_token);
GNCAdvertiserConnectionInfo *connInfo =
[[GNCAdvertiserConnectionInfo alloc] initWithName:name authToken:authToken];
endpointInfo = [GNCAdvertiserEndpointInfo infoWithEndpointConnectionInfo:connInfo];
// Call the connection initiation handler. Synchronous because it returns the connection
// result handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
__weak __typeof__(advertiser) weakAdvertiser = advertiser;
endpointInfo.connectionResultHandlers = advertiser.initiationHandler(
endpointId, (id<GNCAdvertiserConnectionInfo>)connInfo,
^(GNCConnectionResponse response) {
__strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser;
endpointInfo.clientResponse = response;
endpointInfo.clientResponseReceived = YES;
if (response == GNCConnectionResponseAccept) {
// The connection was accepted by the client.
if (payload_listener_ == nullptr) {
payload_listener_ = std::make_unique<GNCPayloadListener>(
advertiser.core,
^{
return endpointInfo.connectionHandlers;
},
^{
return endpointInfo.connection.payloads;
});
}
advertiser.core->_core->AcceptConnection(
CppStringFromObjCString(endpointId),
PayloadListener{
.payload_cb = absl::bind_front(&GNCPayloadListener::OnPayload,
payload_listener_.get()),
.payload_progress_cb = absl::bind_front(
&GNCPayloadListener::OnPayloadProgress, payload_listener_.get()),
},
ResultListener{});
} else {
// The connection was rejected by the client.
advertiser.core->_core->RejectConnection(CppStringFromObjCString(endpointId),
ResultListener{});
}
});
});
advertiser.endpoints[endpointId] = endpointInfo;
}
}
void OnAccepted(const std::string &endpoint_id) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (!endpointInfo) {
GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId);
return;
}
// The connection has been accepted by both endpoints, so create the GNCConnection object
// and pass it to |successHandler| for the client to use. It will be removed from |endpoints|
// when the client disconnects (on dealloc of GNCConnection).
// Note: Use a local strong reference to the connection object; don't just assign to
// |endpointInfo.connection|. Without a strong reference, the connection object can be
// deallocated before |successHandler| is called in the Release build.
__weak __typeof__(advertiser) weakAdvertiser = advertiser;
id<GNCConnection> connection = [GNCCoreConnection
connectionWithEndpointId:endpointId
core:advertiser.core
deallocHandler:^{
__strong __typeof__(advertiser) strongAdvertiser = weakAdvertiser;
if (!strongAdvertiser) return;
[strongAdvertiser.endpoints removeObjectForKey:endpointId];
}];
endpointInfo.connection = connection;
// Callback is synchronous because it returns the connection handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandlers =
endpointInfo.connectionResultHandlers.successHandler(connection);
});
}
void OnRejected(const std::string &endpoint_id, Status status) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (!endpointInfo) {
GTMLoggerInfo(@"Connection result for unknown endpoint: %@", endpointId);
return;
}
// One side rejected, so call failureHandler with the connection status (we do this in all
// cases), and forget the endpoint.
dispatch_async(dispatch_get_main_queue(), ^{
endpointInfo.connectionResultHandlers.failureHandler(GNCConnectionFailureRejected);
});
[advertiser.endpoints removeObjectForKey:endpointId];
}
void OnDisconnected(const std::string &endpoint_id) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCAdvertiserEndpointInfo *endpointInfo = advertiser.endpoints[endpointId];
if (endpointInfo) {
if (endpointInfo.connection) {
GNCDisconnectedHandler disconnectedHandler =
endpointInfo.connectionHandlers.disconnectedHandler;
dispatch_async(dispatch_get_main_queue(), ^{
if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown);
});
} else {
GTMLoggerInfo(@"Disconnect for unconnected endpoint: %@", endpointId);
}
[advertiser.endpoints removeObjectForKey:endpointId];
} else {
GTMLoggerInfo(@"Disconnect for unknown endpoint: %@", endpointId);
}
}
void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) {
GNCAdvertiser *advertiser = advertiser_; // strongify
if (!advertiser) return;
// TODO(b/169292092): Implement.
}
private:
__weak GNCAdvertiser *advertiser_;
std::unique_ptr<GNCPayloadListener> payload_listener_;
};
} // namespace connections
} // namespace nearby
} // namespace location
using ::location::nearby::connections::GNCAdvertiserConnectionListener;
@interface GNCAdvertiser () {
std::unique_ptr<GNCAdvertiserConnectionListener> advertiserListener;
};
@end
@implementation GNCAdvertiser
+ (instancetype)advertiserWithEndpointInfo:(NSData *)endpointInfo
serviceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
connectionInitiationHandler:
(GNCAdvertiserConnectionInitiationHandler)initiationHandler {
GNCAdvertiser *advertiser = [[GNCAdvertiser alloc] init];
advertiser.initiationHandler = initiationHandler;
advertiser.endpoints = [[NSMutableDictionary alloc] init];
advertiser.core = GNCGetCore();
advertiser->advertiserListener = std::make_unique<GNCAdvertiserConnectionListener>(advertiser);
ConnectionListener listener = {
.initiated_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnInitiated,
advertiser->advertiserListener.get()),
.accepted_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnAccepted,
advertiser->advertiserListener.get()),
.rejected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnRejected,
advertiser->advertiserListener.get()),
.disconnected_cb = absl::bind_front(&GNCAdvertiserConnectionListener::OnDisconnected,
advertiser->advertiserListener.get()),
};
advertiser.core->_core->StartAdvertising(CppStringFromObjCString(serviceId),
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
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#include <memory>
#include "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
@@ -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 <utility>
#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<PayloadId, NSURL *> _sending_urls;
}
- (instancetype)init {
GTMLoggerInfo(@"GNCCore created");
self = [super init];
if (self) {
_service_controller_router = std::make_unique<ServiceControllerRouter>();
_core = std::make_unique<Core>(_service_controller_router.get());
}
return self;
}
- (void)dealloc {
_core.reset();
_service_controller_router.reset();
GTMLoggerInfo(@"GNCCore deallocated");
}
- (void)insertURLToMapWithPayloadID:(PayloadId)payloadId urlToSend:(NSURL *)url {
_sending_urls.emplace(payloadId, url);
}
- (nullable NSURL *)extractURLWithPayloadID:(PayloadId)payloadId {
NSURL *url;
auto it = _sending_urls.find(payloadId);
if (it != _sending_urls.end()) {
auto pair = _sending_urls.extract(it);
url = pair.mapped();
}
return url;
}
- (void)clearSendingURLMaps {
_sending_urls.clear();
}
@end
GNCCore *GNCGetCore() {
static NSObject *syncSingleton;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
syncSingleton = [[NSObject alloc] init];
});
// The purpose of keeping a weak reference to the GNCCore object is to ensure that it will be
// released when all external strong references are gone. I.e., when the app is no longer doing
// any NC operations, the core will be released.
static __weak GNCCore *core;
// Strongly reference the GNCCore object for the duration of this function to ensure it isn't
// prematurely deallocated by ARC after being created (which can happen in optimized builds).
GNCCore *strongCore = core;
@synchronized(syncSingleton) {
if (!strongCore) {
strongCore = [[GNCCore alloc] init];
core = strongCore;
}
}
return strongCore;
}
@@ -0,0 +1,44 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "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 <GNCConnection>
@property(nonatomic) GNCCore *core;
@property(nonatomic, copy) GNCEndpointId endpointId;
@property(nonatomic) dispatch_block_t deallocHandler;
@property(nonatomic) NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads;
+ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId
core:(GNCCore *)core
deallocHandler:(dispatch_block_t)deallocHandler;
@end
NS_ASSUME_NONNULL_END
@@ -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<ByteArray> Read() { return Read(kMaxChunkSize); }
ExceptionOr<ByteArray> Read(std::int64_t size) override {
uint8_t *bytesRead = new uint8_t[size];
NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue];
NSInteger numberOfBytesRead = [nsStream_ read:bytesRead maxLength:numberOfBytesToRead];
if (numberOfBytesRead == 0) {
// Reached end of stream.
return ExceptionOr<ByteArray>();
} else if (numberOfBytesRead < 0) {
// Stream error.
return ExceptionOr<ByteArray>{Exception::kIo};
}
return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytesRead
length:numberOfBytesRead]));
}
Exception Close() override {
[nsStream_ close];
return {Exception::kSuccess};
}
private:
static const size_t kMaxChunkSize = 32 * 1024;
NSInputStream *nsStream_;
// dispatch_block_t update_handler_;
};
} // namespace connections
} // namespace nearby
} // namespace location
@implementation GNCPayloadInfo
+ (instancetype)infoWithProgress:(nullable NSProgress *)progress
completion:(GNCPayloadResultHandler)completion {
GNCPayloadInfo *info = [[GNCPayloadInfo alloc] init];
info.progress = progress;
info.completion = completion;
return info;
}
- (void)callCompletion:(GNCPayloadResult)result {
if (_completion) _completion(result);
_completion = nil;
}
@end
@implementation GNCCoreConnection
+ (instancetype)connectionWithEndpointId:(GNCEndpointId)endpointId
core:(GNCCore *)core
deallocHandler:(dispatch_block_t)deallocHandler {
GNCCoreConnection *connection = [[GNCCoreConnection alloc] init];
connection.endpointId = endpointId;
connection.core = core;
connection.deallocHandler = deallocHandler;
connection.payloads = [[NSMutableDictionary alloc] init];
return connection;
}
- (void)dealloc {
_core->_core->DisconnectFromEndpoint(CppStringFromObjCString(_endpointId), ResultListener{});
_deallocHandler();
}
- (NSProgress *)sendBytesPayload:(GNCBytesPayload *)payload
completion:(GNCPayloadResultHandler)completion {
Payload corePayload(ByteArrayFromNSData(payload.bytes));
NSUInteger length = payload.bytes.length;
PayloadId payloadId = corePayload.GetId();
NSProgress *progress = [NSProgress progressWithTotalUnitCount:length];
__weak __typeof__(self) weakSelf = self;
progress.cancellationHandler = ^{
[weakSelf cancelPayloadWithId:payloadId];
};
return [self sendPayload:std::move(corePayload)
size:length
progress:progress
completion:completion];
}
- (NSProgress *)sendStreamPayload:(GNCStreamPayload *)payload
completion:(GNCPayloadResultHandler)completion {
NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1];
PayloadId payloadId = Payload::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<std::string>(1, CppStringFromObjCString(_endpointId)),
std::move(payload), ResultListener{});
return progress;
}
- (void)cancelPayloadWithId:(PayloadId)payloadId {
_core->_core->CancelPayload(payloadId, ResultListener{});
}
@end
@@ -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 <string>
#include <utility>
#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 <GNCDiscovererConnectionInfo>
@property(nonatomic, copy) NSString *authToken;
/** Creates a GNCDiscovererConnectionInfo object. */
+ (instancetype)infoWithAuthToken:(NSString *)authToken;
@end
@implementation GNCDiscovererConnectionInfo
+ (instancetype)infoWithAuthToken:(NSString *)authToken {
GNCDiscovererConnectionInfo *info = [[GNCDiscovererConnectionInfo alloc] init];
info.authToken = authToken;
return info;
}
@end
/** This is a GNCDiscoveredEndpointInfo that provides storage for its properties. */
@interface GNCDiscoveredEndpointInfo : NSObject <GNCDiscoveredEndpointInfo>
@property(nonatomic, copy) NSString *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<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints;
@end
/** C++ classes passed to the core library by GNCDiscoverer. */
namespace location {
namespace nearby {
namespace connections {
/** This class contains the discoverer callbacks related to a connection. */
class GNCDiscovererConnectionListener {
public:
GNCDiscovererConnectionListener(GNCCore *core,
NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints)
: core_(core), endpoints_(endpoints) {}
void OnInitiated(const std::string &endpoint_id, const ConnectionResponseInfo &info) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// Call the connection initiation handler. Synchronous because it returns the connection
// handler.
NSString *authToken = ObjCStringFromCppString(info.authentication_token);
GNCCore *core = core_; // don't capture |this|
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandler = endpointInfo.connInitHandler(
[GNCDiscovererConnectionInfo infoWithAuthToken:authToken],
^(GNCConnectionResponse response) {
endpointInfo.clientResponse = response;
endpointInfo.clientResponseReceived = YES;
if (response == GNCConnectionResponseAccept) {
// The connect was accepted by the client.
if (payload_listener_ == nullptr) {
payload_listener_ = std::make_unique<GNCPayloadListener>(
core,
^{
return endpointInfo.connectionHandlers;
},
^{
return endpointInfo.connection.payloads;
});
}
core->_core->AcceptConnection(
CppStringFromObjCString(endpointId),
PayloadListener{
.payload_cb =
absl::bind_front(&GNCPayloadListener::OnPayload, payload_listener_.get()),
.payload_progress_cb = absl::bind_front(
&GNCPayloadListener::OnPayloadProgress, payload_listener_.get()),
},
ResultListener{});
} else {
// The connect was rejected by the client.
core->_core->RejectConnection(CppStringFromObjCString(endpointId), ResultListener{});
}
});
});
}
void OnAccepted(const std::string &endpoint_id) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// The connection has been accepted by both endpoints, so create the GNCConnection object
// and pass it to |successHandler| for the client to use.
// Note: Use a local strong reference to the connection object; don't just assign to
// |endpointInfo.connection|. Without a strong reference, the connection object can be
// deallocated before |successHandler| is called in the Release build.
id<GNCConnection> connection = [GNCCoreConnection
connectionWithEndpointId:endpointId
core:core_
deallocHandler:^{
// Don't remove the remote endpoint (like GNCAdvertiser does) because that's
// done when the endpoint is lost.
}];
endpointInfo.connection = connection;
// Callback is synchronous because it returns the connection handlers.
dispatch_sync(dispatch_get_main_queue(), ^{
endpointInfo.connectionHandlers = endpointInfo.connectionHandler(endpointInfo.connection);
});
endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection
}
void OnRejected(const std::string &endpoint_id, Status status) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
// If either side rejected, call failureHandler with the connection status.
dispatch_async(dispatch_get_main_queue(), ^{
endpointInfo.connFailureHandler(GNCConnectionFailureRejected);
});
endpointInfo.clientResponseReceived = NO; // support reconnection after disconnection
}
void OnDisconnected(const std::string &endpoint_id) {
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
GNCDiscovererEndpointInfo *endpointInfo = [endpoints_ objectForKey:endpointId];
if (!endpointInfo) {
return;
}
if (endpointInfo.connection) {
GNCDisconnectedHandler disconnectedHandler =
endpointInfo.connectionHandlers.disconnectedHandler;
dispatch_async(dispatch_get_main_queue(), ^{
if (disconnectedHandler) disconnectedHandler(GNCDisconnectedReasonUnknown);
});
}
}
void OnBandwidthChanged(const std::string &endpoint_id, Medium medium) {
// TODO(b/169292092): Implement.
}
private:
GNCCore *core_;
NSMapTable<GNCEndpointId, GNCDiscovererEndpointInfo *> *endpoints_;
std::unique_ptr<GNCPayloadListener> payload_listener_;
};
class GNCDiscoveryListener {
public:
explicit GNCDiscoveryListener(GNCDiscoverer *discoverer) : discoverer_(discoverer) {}
void OnEndpointFound(const std::string &endpoint_id, const ByteArray &endpoint_info,
const std::string &service_id) {
GNCDiscoverer *discoverer = discoverer_; // strongify
if (!discoverer) {
return;
}
NSString *endpointId = ObjCStringFromCppString(endpoint_id);
if ([discoverer.endpoints objectForKey:endpointId] != nil) {
GTMLoggerError(@"Endpoint already discovered: %@", endpointId);
} else {
// The GNCDiscoveredEndpointInfo object created here lives as long as the client has strong
// reference to it. Here's the chain of strong references maintained here:
// client -> GNCDiscoveredEndpointInfo -> RequestConnection block ->
// GNCDiscovererEndpointInfo (stored weakly in the |endpoints| map table)
GNCDiscovererEndpointInfo *endpointInfo = [[GNCDiscovererEndpointInfo alloc] init];
[discoverer.endpoints setObject:endpointInfo forKey:endpointId];
// 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<GNCEndpointId, GNCDiscovererEndpointInfo *> *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<GNCDiscovererConnectionListener>(core, endpoints);
}
ConnectionListener listener = {
.initiated_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnInitiated,
discoverer_connection_listener_.get()),
.accepted_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnAccepted,
discoverer_connection_listener_.get()),
.rejected_cb = absl::bind_front(&GNCDiscovererConnectionListener::OnRejected,
discoverer_connection_listener_.get()),
.disconnected_cb =
absl::bind_front(&GNCDiscovererConnectionListener::OnDisconnected,
discoverer_connection_listener_.get()),
};
core->_core->RequestConnection(
CppStringFromObjCString(endpointId),
ConnectionRequestInfo{.endpoint_info = 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<GNCDiscovererConnectionListener> discoverer_connection_listener_;
};
} // namespace connections
} // namespace nearby
} // namespace location
using ::location::nearby::connections::GNCDiscoveryListener;
@interface GNCDiscoverer () {
std::unique_ptr<GNCDiscoveryListener> discoveryListener;
};
@end
@implementation GNCDiscoverer
+ (instancetype)discovererWithServiceId:(NSString *)serviceId
strategy:(GNCStrategy)strategy
endpointFoundHandler:(GNCEndpointFoundHandler)endpointFoundHandler {
GNCDiscoverer *discoverer = [[GNCDiscoverer alloc] init];
discoverer.endpointFoundHandler = endpointFoundHandler;
discoverer.endpoints = [NSMapTable strongToWeakObjectsMapTable];
discoverer.core = GNCGetCore();
discoverer->discoveryListener = std::make_unique<GNCDiscoveryListener>(discoverer);
DiscoveryListener listener = {
.endpoint_found_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointFound,
discoverer->discoveryListener.get()),
.endpoint_lost_cb = absl::bind_front(&GNCDiscoveryListener::OnEndpointLost,
discoverer->discoveryListener.get()),
};
discoverer.core->_core->StartDiscovery(CppStringFromObjCString(serviceId),
ConnectionOptions{
.strategy = GNCStrategyToStrategy(strategy),
},
std::move(listener), ResultListener{});
return discoverer;
}
- (void)dealloc {
GTMLoggerInfo(@"GNCDiscoverer deallocated");
_core->_core->StopDiscovery(ResultListener{});
}
@end
NS_ASSUME_NONNULL_END
@@ -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
@@ -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 <stdlib.h>
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
@@ -0,0 +1,55 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "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<NSNumber *, GNCPayloadInfo *> *_Nonnull (^GNCPayloadsProvider)();
/** This is the payload handler for an advertiser or discoverer. */
class GNCPayloadListener : public PayloadListener {
public:
GNCPayloadListener(GNCCore *core, GNCConnectionHandlersProvider handlersProvider,
GNCPayloadsProvider payloadsProvider)
: core_(core), handlers_provider_(handlersProvider), payloads_provider_(payloadsProvider) {}
void OnPayload(const std::string& endpoint_id, Payload payload);
void OnPayloadProgress(const std::string& endpoint_id,
const PayloadProgressInfo& info);
private:
GNCCore *core_;
GNCConnectionHandlersProvider handlers_provider_;
GNCPayloadsProvider payloads_provider_;
};
} // namespace connections
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -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 <string>
#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<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_();
switch (payload.GetType()) {
case Payload::Type::kBytes: {
NSData *data = NSDataFromByteArray(payload.AsBytes()); // don't capture C++ object
// Wait for the payload transfer update to arrive before calling the Bytes payload handler.
GNCPayloadInfo *info = [GNCPayloadInfo
infoWithProgress:nil
completion:^(GNCPayloadResult result) {
NSCAssert(result == GNCPayloadResultSuccess, @"Expected success");
if (handlers.bytesPayloadHandler) {
// Call the Bytes payload handler.
dispatch_async(dispatch_get_main_queue(), ^{
handlers.bytesPayloadHandler([GNCBytesPayload payloadWithBytes:data
identifier:payloadId]);
});
}
}];
payloads[@(payloadId)] = info;
break;
}
case Payload::Type::kStream:
if (handlers.streamPayloadHandler) {
// Make a pair of bound streams so data pumped into the output stream becomes
// available for reading from the input stream.
NSInputStream *clientInputStream;
NSOutputStream *clientOutputStream;
// TODO(b/169292092): Base on medium's bandwidth?
[NSStream getBoundStreamsWithBufferSize:1024
inputStream:&clientInputStream
outputStream:&clientOutputStream];
NSProgress *progress = [NSProgress progressWithTotalUnitCount:-1]; // indeterminate
progress.cancellable = YES;
// Pass the payload to the stream payload handler, receiving the completion handler from it.
// Since it returns a value, it must be called synchronously.
__block GNCPayloadResultHandler completion;
dispatch_sync(dispatch_get_main_queue(), ^{
completion = handlers.streamPayloadHandler(
[GNCStreamPayload payloadWithStream:clientInputStream identifier:payloadId],
progress);
});
GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion];
payloads[@(payloadId)] = info;
// This is a loop that reads data from the C++ input stream and writes it to the output
// stream that feeds it to the client input stream.
__block InputStream *payloadInputStream = payload.AsStream();
dispatch_queue_t queue =
dispatch_queue_create("StreamReceiverQueue", DISPATCH_QUEUE_SERIAL);
dispatch_async(queue, ^{
[clientOutputStream open];
while (true) {
if (progress.isCancelled) {
// Payload was canceled by the client.
core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) {
// TODO(b/148640962): Implement.
}});
break;
}
ExceptionOr<ByteArray> readResult = payloadInputStream->Read(1024);
if (!readResult.ok()) {
// Error reading from stream.
// TODO(b/169292092): Tell core an error has occurred?
dispatch_async(dispatch_get_main_queue(), ^{
[info callCompletion:GNCPayloadResultFailure];
});
break;
}
ByteArray byteArray = readResult.GetResult();
if (byteArray.Empty()) {
// End of stream.
break;
}
// Loop until it's all been consumed by the client output stream.
NSData *data = NSDataFromByteArray(byteArray);
NSUInteger totalLength = data.length;
NSUInteger totalNumberWritten = 0;
while (totalNumberWritten < totalLength) {
NSInteger numberWritten =
[clientOutputStream write:&((const uint8_t *)data.bytes)[totalNumberWritten]
maxLength:totalLength - totalNumberWritten];
if (numberWritten <= 0) { // stream error or reached end of stream
// TODO(b/169292092): Tell core an error has occurred?
dispatch_async(dispatch_get_main_queue(), ^{
[info callCompletion:GNCPayloadResultFailure];
});
break;
}
totalNumberWritten += numberWritten;
}
}
});
}
break;
case Payload::Type::kFile:
if (handlers.filePayloadHandler) {
InputFile *payloadInputFile = payload.AsFile();
NSURL *fileURL =
[NSURL URLWithString:ObjCStringFromCppString(payloadInputFile->GetFilePath())];
int64_t fileSize = payloadInputFile->GetTotalSize();
NSProgress *progress = [NSProgress progressWithTotalUnitCount:fileSize];
progress.cancellable = YES;
progress.cancellationHandler = ^{
// Payload was canceled by the client.
core_->_core->CancelPayload(payloadId, ResultCallback{.result_cb = [](Status status) {
// TODO(b/148640962): Implement.
}});
};
// Pass the payload to the file payload handler, receiving the completion handler from it.
// Since it returns a value, it must be called synchronously.
__block GNCPayloadResultHandler completion;
void (^passPayloadBlock)(void) = ^{
completion = handlers.filePayloadHandler(
[GNCFilePayload payloadWithFileURL:fileURL identifier:payloadId], progress);
};
if ([NSThread isMainThread]) {
passPayloadBlock();
} else {
dispatch_sync(dispatch_get_main_queue(), passPayloadBlock);
}
GNCPayloadInfo *info = [GNCPayloadInfo infoWithProgress:progress completion:completion];
payloads[@(payloadId)] = info;
}
break;
default:
;// fall through
}
}
void GNCPayloadListener::OnPayloadProgress(const std::string &endpoint_id,
const PayloadProgressInfo &info) {
// Note: The logic in this callback for handling progress updates and payload completion is
// identical for Bytes, Stream and File payloads.
NSMutableDictionary<NSNumber *, GNCPayloadInfo *> *payloads = payloads_provider_();
NSNumber *payloadId = @(info.payload_id);
GNCPayloadInfo *payloadInfo = payloads[payloadId];
if (payloadInfo) {
// Update the progress.
if (payloadInfo.progress) {
payloadInfo.progress.completedUnitCount = info.bytes_transferred;
}
// Call the completion handler for success/failure/canceled, but not in-progress.
if (info.status == PayloadProgressInfo::Status::kInProgress) {
return;
}
GNCPayloadResult result =
(info.status == PayloadProgressInfo::Status::kSuccess) ? GNCPayloadResultSuccess
: (info.status == PayloadProgressInfo::Status::kCanceled) ? GNCPayloadResultCanceled
: GNCPayloadResultFailure;
dispatch_async(dispatch_get_main_queue(), ^{
payloadInfo.completion(result);
});
// Release the payload info.
[payloads removeObjectForKey:payloadId];
}
}
} // namespace connections
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -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 <Foundation/Foundation.h>
#include <string>
#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
@@ -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
@@ -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 <string>
#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<AtomicBoolean> ImplementationPlatform::CreateAtomicBoolean(bool initial_value) {
return std::make_unique<ios::AtomicBoolean>(initial_value);
}
std::unique_ptr<AtomicUint32> ImplementationPlatform::CreateAtomicUint32(
std::uint32_t initial_value) {
return std::make_unique<ios::AtomicUint32>(initial_value);
}
std::unique_ptr<CountDownLatch> ImplementationPlatform::CreateCountDownLatch(std::int32_t count) {
return std::make_unique<ios::CountDownLatch>(count);
}
std::unique_ptr<Mutex> ImplementationPlatform::CreateMutex(Mutex::Mode mode) {
// iOS does not support unchecked Mutex in debug mode, therefore
// ios::Mutex is used for both kRegular and kRegularNoCheck.
if (mode == Mutex::Mode::kRecursive) {
return absl::make_unique<ios::RecursiveMutex>();
} else {
return absl::make_unique<ios::Mutex>();
}
}
std::unique_ptr<ConditionVariable> ImplementationPlatform::CreateConditionVariable(Mutex* mutex) {
return std::make_unique<ios::ConditionVariable>(static_cast<ios::Mutex*>(mutex));
}
std::unique_ptr<InputFile> ImplementationPlatform::CreateInputFile(PayloadId payload_id,
std::int64_t total_size) {
// Extract the NSURL object with payload_id from |GNCCore| which stores the maps. If the retrieved
// NSURL object is not nil, we create InputFile by ios::InputFile. The difference is
// that ios::InputFile implements to read bytes from local real file for sending.
GNCCore* core = GNCGetCore();
NSURL* url = [core extractURLWithPayloadID:payload_id];
if (url != nil) {
return absl::make_unique<ios::InputFile>(url);
} else {
return absl::make_unique<shared::InputFile>(GetPayloadPath(payload_id), total_size);
}
}
std::unique_ptr<OutputFile> ImplementationPlatform::CreateOutputFile(PayloadId payload_id) {
return absl::make_unique<shared::OutputFile>(GetPayloadPath(payload_id));
}
std::unique_ptr<LogMessage> ImplementationPlatform::CreateLogMessage(
const char* file, int line, LogMessage::Severity severity) {
return absl::make_unique<ios::LogMessage>(file, line, severity);
}
// Java-like Executors
std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateSingleThreadExecutor() {
return std::make_unique<ios::SingleThreadExecutor>();
}
std::unique_ptr<SubmittableExecutor> ImplementationPlatform::CreateMultiThreadExecutor(
int max_concurrency) {
return std::make_unique<ios::MultiThreadExecutor>(max_concurrency);
}
std::unique_ptr<ScheduledExecutor> ImplementationPlatform::CreateScheduledExecutor() {
return std::make_unique<ios::ScheduledExecutor>();
}
// Mediums
std::unique_ptr<BluetoothAdapter> ImplementationPlatform::CreateBluetoothAdapter() {
return nullptr;
}
std::unique_ptr<BluetoothClassicMedium> ImplementationPlatform::CreateBluetoothClassicMedium(
api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<BleMedium> ImplementationPlatform::CreateBleMedium(api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<ble_v2::BleMedium> ImplementationPlatform::CreateBleV2Medium(
api::BluetoothAdapter& adapter) {
return nullptr;
}
std::unique_ptr<ServerSyncMedium> ImplementationPlatform::CreateServerSyncMedium() {
return nullptr;
}
std::unique_ptr<WifiMedium> ImplementationPlatform::CreateWifiMedium() { return nullptr; }
std::unique_ptr<WifiLanMedium> ImplementationPlatform::CreateWifiLanMedium() {
return std::make_unique<ios::WifiLanMedium>();
}
std::unique_ptr<WebRtcMedium> ImplementationPlatform::CreateWebRtcMedium() { return nullptr; }
} // namespace api
} // namespace nearby
} // namespace location
@@ -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",
],
)
@@ -0,0 +1,18 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
// Verifies that an object has been deallocated after the given time period.
void GNCVerifyDealloc(id object, NSTimeInterval timeInterval);
@@ -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
}
@@ -0,0 +1,101 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/** Result of a medium payload transfer. */
typedef NS_ENUM(NSInteger, GNCMPayloadResult) {
GNCMPayloadResultSuccess, // Payload delivery was successful.
GNCMPayloadResultFailure, // An error occurred during payload delivery.
GNCMPayloadResultCanceled, // Payload delivery was canceled.
};
/** Handler for a @c GNCMPayloadResult value. */
typedef void (^GNCMPayloadResultHandler)(GNCMPayloadResult);
/**
* A progress handler is periodically called during payload delivery. It is passed a value
* ranging from 0 (when the operation has just started) to the total size (when the operation is
* finished).
*/
typedef void (^GNCMProgressHandler)(size_t count);
/** This handler is called when data is received from a remote endpoint. */
typedef void (^GNCMPayloadHandler)(NSData *data);
/**
* This represents a connection with a remote endpoint at the medium level. Use it to send
* payloads to the remote endpoint, and release it to disconnect.
*/
@protocol GNCMConnection <NSObject>
/**
* Sends data to the remote endpoint. Wait for the completion to be called before sending another
* payload.
*
* @param payload The data to send.
* @param progressHandler Called repeatedly for progress feedback while the data is being sent.
* @param completion Callback called when the data has been fully sent,
* or it has failed to be sent (not connected or disconnected).
*/
- (void)sendData:(NSData *)payload
progressHandler:(GNCMProgressHandler)progressHandler
completion:(GNCMPayloadResultHandler)completion;
@end
/** This class contains optional handlers for a connection. */
@interface GNCMConnectionHandlers : NSObject
/** This handler is called when data is sent from the remote endpoint. */
@property(nonatomic) GNCMPayloadHandler payloadHandler;
/** This handler is called when the connection is ended. */
@property(nonatomic) dispatch_block_t disconnectedHandler;
/** This method creates a GNCMConnectionHandlers object from payload and disconnect handlers. */
+ (instancetype)payloadHandler:(GNCMPayloadHandler)payloadHandler
disconnectedHandler:(dispatch_block_t)disconnectedHandler;
@end
/**
* This handler takes a GNCMConnection object and returns a GNCMConnectionHandlers object. It is
* called when a connection is successfully made with a remote endpoint. If |connection| is nil,
* the connection couldn't be established; in this case, return nil.
*/
typedef GNCMConnectionHandlers *_Nullable (^GNCMConnectionHandler)(
id<GNCMConnection> __nullable connection);
/**
* This handler is called by a discovering endpoint to request a connection with an an advertising
* endpoint.
*/
typedef void (^GNCMConnectionRequester)(GNCMConnectionHandler connectionHandler);
/** This handler is called when a previously discovered advertising endpoint is lost. */
typedef void (^GNCMEndpointLostHandler)(void);
/**
* This handler is called on a discoverer when a nearby advertising endpoint is
* discovered. Calls |requestConnection| to request a connection with the advertiser.
*/
typedef GNCMEndpointLostHandler _Nonnull (^GNCMEndpointFoundHandler)(
NSString *endpointId, NSString *serviceType, NSString *serviceName,
NSDictionary<NSString *, NSData *> *_Nullable TXTRecordData,
GNCMConnectionRequester requestConnection);
NS_ASSUME_NONNULL_END
@@ -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
@@ -0,0 +1,42 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "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
@@ -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 () <NSNetServiceBrowserDelegate, NSNetServiceDelegate>
@property(nonatomic, copy) GNCMEndpointFoundHandler endpointFoundHandler;
@property(nonatomic) NSNetServiceBrowser *netBrowser;
@property(nonatomic) NSMutableDictionary<GNCEndpointId, GNCMNetServiceInfo *> *endpoints;
@end
@implementation GNCMBonjourBrowser
- (instancetype)initWithServiceType:(NSString *)serviceType
endpointFoundHandler:(GNCMEndpointFoundHandler)handler {
self = [super init];
if (self) {
_endpointFoundHandler = handler;
_netBrowser = [[NSNetServiceBrowser alloc] init];
_netBrowser.delegate = self;
[_netBrowser scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_netBrowser searchForServicesOfType:serviceType inDomain:GNCMBonjourDomain];
_endpoints = [NSMutableDictionary dictionary];
}
return self;
}
#pragma mark NSNetServiceBrowserDelegate
- (void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)browser {
GTMLoggerDebug(@"Browsing");
}
- (void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)browser {
GTMLoggerDebug(@"Stop browsing");
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didNotSearch:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Not browsing with errorDict: %@", errorDict);
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didFindService:(NSNetService *)service
moreComing:(BOOL)moreComing {
GTMLoggerDebug(@"Found service: %@", service);
GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service];
// Just to be safe, check if the service is already known and deal with it accordingly.
NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info];
if (endpointIds.count > 0) return;
// Add the newly discovered service to the list of services.
GNCEndpointId endpointId = [[NSUUID UUID] UUIDString];
_endpoints[endpointId] = info;
service.delegate = self;
[service resolveWithTimeout:0];
}
- (void)netServiceBrowser:(NSNetServiceBrowser *)browser
didRemoveService:(NSNetService *)service
moreComing:(BOOL)moreComing {
GTMLoggerDebug(@"Lost service: %@", service);
NSArray<GNCEndpointId> *endpointIds =
[_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]];
NSAssert(endpointIds.count <= 1, @"Unexpected duplicate service");
if (endpointIds.count > 0) {
GNCEndpointId endpointId = endpointIds[0];
GNCMNetServiceInfo *info = _endpoints[endpointId];
[_endpoints removeObjectForKey:endpointId];
// Tail call to preserve reentrancy.
if (info.endpointLostHandler) {
info.endpointLostHandler();
}
}
}
#pragma mark NSNetServiceDelegate
- (void)netServiceDidResolveAddress:(NSNetService *)service {
GTMLoggerDebug(@"Resolved service: %@ addresses: %@", service, service.addresses);
GNCMNetServiceInfo *info = [GNCMNetServiceInfo infoWithService:service];
NSArray<GNCEndpointId> *endpointIds = [_endpoints allKeysForObject:info];
if (endpointIds.count > 0) {
// Get TXTRecord data.
NSData *data = [service TXTRecordData];
NSDictionary<NSString *, NSData *> *TXTRecordData =
[NSNetService dictionaryFromTXTRecordData:data];
// The endpointLostHandler is returned from the endpointFoundHandler. The main benefit of this
// is that it allows rejection from the remote endpoint to be received by the local endpoint
// before the local endpoint has accepted or rejected.
info.endpointLostHandler = _endpointFoundHandler(
endpointIds[0], service.type, service.name, TXTRecordData,
^(GNCMConnectionHandler connectionHandler) {
// A connection is requested, so resolve the service to get the I/O streams.
info.connectionHandler = connectionHandler;
if (info.connectionHandler) {
dispatch_sync(dispatch_get_main_queue(), ^{
NSInputStream *inputStream;
NSOutputStream *outputStream;
[info.service scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[info.service getInputStream:&inputStream outputStream:&outputStream];
GNCMBonjourConnection *connection =
[[GNCMBonjourConnection alloc] initWithInputStream:inputStream
outputStream:outputStream
queue:nil];
connection.connectionHandlers = info.connectionHandler(connection);
});
}
});
_endpoints[endpointIds[0]] = info;
}
}
- (void)netService:(NSNetService *)service
didNotResolve:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Did not resolve service: %@", service);
NSArray<GNCEndpointId> *endpointIds =
[_endpoints allKeysForObject:[GNCMNetServiceInfo infoWithService:service]];
if (endpointIds.count > 0) {
_endpoints[endpointIds[0]].connectionHandler = nil;
}
}
@end
@@ -0,0 +1,38 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
#import "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 <GNCMConnection>
@property(nonatomic) GNCMConnectionHandlers *connectionHandlers;
- (instancetype)initWithInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream
queue:(nullable dispatch_queue_t)queue;
@end
NS_ASSUME_NONNULL_END
@@ -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 () <NSStreamDelegate>
@property(nonatomic) NSInputStream *inputStream;
@property(nonatomic) NSOutputStream *outputStream;
@property(nonatomic) dispatch_queue_t callbackQueue;
@property(nonatomic, copy, nullable) NSData *dataBeingWritten;
@property(nonatomic) NSInteger numberOfBytesLeftToWrite;
@property(nonatomic, copy, nullable) GNCMProgressHandler progressHandler;
@property(nonatomic, copy, nullable) GNCMPayloadResultHandler completion;
@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
@@ -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 <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#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<NSString *, NSData *> *)TXTRecordData
endpointConnectedHandler:(GNCMConnectionHandler)handler;
@end
NS_ASSUME_NONNULL_END
@@ -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 () <NSNetServiceDelegate>
@property(nonatomic, copy) NSString *serviceName;
@property(nonatomic, copy) NSString *serviceType;
@property(nonatomic, copy) GNCMConnectionHandler endpointConnectedHandler;
@property(nonatomic) NSNetService *netService;
@end
@implementation GNCMBonjourService
- (instancetype)initWithServiceName:(NSString *)serviceName
serviceType:(NSString *)serviceType
port:(NSInteger)port
TXTRecordData:(NSDictionary<NSString *, NSData *> *)TXTRecordData
endpointConnectedHandler:(GNCMConnectionHandler)handler {
self = [super init];
if (self) {
_serviceName = [serviceName copy];
_serviceType = [serviceType copy];
_endpointConnectedHandler = handler;
_netService = [[NSNetService alloc] initWithDomain:GNCMBonjourDomain
type:_serviceType
name:_serviceName
port:port];
[_netService setTXTRecordData:[NSNetService dataFromTXTRecordDictionary:TXTRecordData]];
_netService.delegate = self;
[_netService scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[_netService publishWithOptions:NSNetServiceListenForConnections];
}
return self;
}
- (void)dealloc {
[_netService stop];
}
#pragma mark NSNetServiceDelegate
- (void)netServiceDidPublish:(NSNetService *)service {
GTMLoggerDebug(@"Did publish service: %@", service);
}
- (void)netService:(NSNetService *)service
didNotPublish:(NSDictionary<NSString *, NSNumber *> *)errorDict {
GTMLoggerDebug(@"Error publishing: service: %@, errorDic: %@", service, errorDict);
}
- (void)netServiceDidStop:(NSNetService *)service {
GTMLoggerDebug(@"Stopped publishing service: %@", service);
}
- (void)netService:(NSNetService *)service
didAcceptConnectionWithInputStream:(NSInputStream *)inputStream
outputStream:(NSOutputStream *)outputStream {
GTMLoggerDebug(@"Accepted connection, service: %@", service);
GNCMBonjourConnection *connection =
[[GNCMBonjourConnection alloc] initWithInputStream:inputStream
outputStream:outputStream
queue:nil];
connection.connectionHandlers = _endpointConnectedHandler(connection);
}
@end
@@ -0,0 +1,18 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
// mDNS domain.
FOUNDATION_EXPORT NSString *_Nonnull const GNCMBonjourDomain;
@@ -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";
@@ -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",
],
)
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_
#define PLATFORM_IMPL_IOS_ATOMIC_BOOLEAN_H_
#include <atomic>
#include "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_
@@ -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
@@ -0,0 +1,47 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
#define PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
#include <atomic>
#include <cstdint>
#include "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<std::uint32_t> value_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_ATOMIC_UINT32_H_
@@ -0,0 +1,64 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#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
@@ -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
@@ -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_
@@ -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
@@ -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<bool> CountDownLatch::Await(absl::Duration timeout) {
bool condition = mutex_.LockWhenWithTimeout(
absl::Condition(IsZeroOrNegative, &count_), timeout);
mutex_.Unlock();
return ExceptionOr<bool>(condition);
}
void CountDownLatch::CountDown() {
absl::MutexLock lock(&mutex_);
count_--;
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -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<bool> Await(absl::Duration timeout) override;
void CountDown() override;
private:
static bool IsZeroOrNegative(int* count) { return 0 >= *count; }
absl::Mutex mutex_;
int count_ ABSL_GUARDED_BY(mutex_);
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_COUNT_DOWN_LATCH_H_
@@ -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
@@ -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
@@ -0,0 +1,48 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_INPUT_FILE_H_
#define PLATFORM_IMPL_IOS_INPUT_FILE_H_
#import <Foundation/Foundation.h>
#include "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<ByteArray> Read(std::int64_t size) override;
std::string GetFilePath() const override;
std::int64_t GetTotalSize() const override;
Exception Close() override;
private:
NSURL *nsURL_;
NSInputStream *nsStream_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_INPUT_FILE_H_
@@ -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 <string>
#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<ByteArray> InputFile::Read(std::int64_t size) {
uint8_t *bytes_read = new uint8_t[size];
NSUInteger numberOfBytesToRead = [[NSNumber numberWithLongLong:size] unsignedIntegerValue];
NSInteger numberOfBytesRead = [nsStream_ read:bytes_read maxLength:numberOfBytesToRead];
if (numberOfBytesRead == 0) {
// Reached end of stream.
return ExceptionOr<ByteArray>();
} else if (numberOfBytesRead < 0) {
// Stream error.
return ExceptionOr<ByteArray>(Exception::kIo);
}
return ExceptionOr<ByteArray>(ByteArrayFromNSData([NSData dataWithBytes:bytes_read
length:numberOfBytesRead]));
}
std::string InputFile::GetFilePath() const {
return CppStringFromObjCString([nsURL_ absoluteString]);
}
std::int64_t InputFile::GetTotalSize() const {
NSNumber *fileSizeValue = nil;
BOOL result = [nsURL_ getResourceValue:&fileSizeValue forKey:NSURLFileSizeKey error:nil];
if (result) {
return fileSizeValue.longValue;
} else {
return 0;
}
}
Exception InputFile::Close() {
[nsStream_ close];
return {Exception::kSuccess};
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -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_
@@ -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
@@ -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<ScheduledExecutor> scheduled_executor_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_MULTI_THREAD_EXECUTOR_H_
@@ -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<ScheduledExecutor>(max_concurrency);
}
void MultiThreadExecutor::Shutdown() { scheduled_executor_->Shutdown(); }
void MultiThreadExecutor::Execute(Runnable&& runnable) {
scheduled_executor_->Execute(std::move(runnable));
}
bool MultiThreadExecutor::DoSubmit(Runnable&& runnable) {
return scheduled_executor_->DoSubmit(std::move(runnable));
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -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<intptr_t>(&per_thread);
}
std::atomic<intptr_t> thread_id_{0};
int count_{0};
absl::Mutex mutex_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_MUTEX_H_
@@ -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
@@ -0,0 +1,68 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_
#define PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_
#import <Foundation/Foundation.h>
#include <functional>
#include <memory>
#include "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<api::Cancelable> Schedule(Runnable&& runnable, absl::Duration duration) override;
void Execute(Runnable&& runnable) override;
bool DoSubmit(Runnable&& runnable);
private:
void Shutdown(std::int64_t timeout_millis);
GNCOperationQueueImpl* impl_;
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_SCHEDULED_EXECUTOR_H_
@@ -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 <Foundation/Foundation.h>
#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<location::nearby::ios::AtomicBoolean> _canceled;
}
@end
@implementation GNCRunnableWrapper
+ (instancetype)wrapperWithRunnable:(location::nearby::Runnable)runnable {
GNCRunnableWrapper *wrapper = [[GNCRunnableWrapper alloc] init];
wrapper->_runnable = runnable;
wrapper->_canceled = std::make_unique<location::nearby::ios::AtomicBoolean>(false);
return wrapper;
}
@end
@implementation GNCOperationQueueImpl
+ (instancetype)implWithMaxConcurrency:(int)maxConcurrency {
GNCOperationQueueImpl *impl = [[GNCOperationQueueImpl alloc] init];
impl.queue = [[NSOperationQueue alloc] init];
impl.queue.maxConcurrentOperationCount = maxConcurrency;
return impl;
}
@end
namespace location {
namespace nearby {
namespace ios {
static const std::int64_t kExecutorShutdownDefaultTimeout = 500; // 0.5 seconds
// This Cancelable references a Runnable and a cancel method that sets its canceled boolean to true.
class CancelableForRunnable : public api::Cancelable {
public:
explicit CancelableForRunnable(GNCRunnableWrapper *runnable) : runnable_(runnable) {}
CancelableForRunnable() = default;
~CancelableForRunnable() override = default;
CancelableForRunnable(const CancelableForRunnable &) = delete;
CancelableForRunnable &operator=(const CancelableForRunnable &) = delete;
// api::Cancelable:
bool Cancel() override {
runnable_->_canceled->Set(true);
return true;
}
private:
GNCRunnableWrapper *runnable_;
};
ScheduledExecutor::ScheduledExecutor() { impl_ = [GNCOperationQueueImpl implWithMaxConcurrency:1]; }
ScheduledExecutor::ScheduledExecutor(int max_concurrency) {
impl_ = [GNCOperationQueueImpl implWithMaxConcurrency:max_concurrency];
}
ScheduledExecutor::~ScheduledExecutor() { impl_ = nil; }
void ScheduledExecutor::Shutdown() { Shutdown(kExecutorShutdownDefaultTimeout); }
std::shared_ptr<api::Cancelable> ScheduledExecutor::Schedule(Runnable &&runnable,
absl::Duration duration) {
if (impl_.shuttingDown) return std::shared_ptr<api::Cancelable>(nullptr);
// Wrap the runnable in an Obj-C object so it can be referenced by the delayed block.
GNCRunnableWrapper *wrapper = [GNCRunnableWrapper wrapperWithRunnable:std::move(runnable)];
CancelableForRunnable *cancelable = new CancelableForRunnable(wrapper);
GNCOperationQueueImpl *impl = impl_; // don't capture |this|
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, absl::ToInt64Milliseconds(duration) * NSEC_PER_MSEC),
dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0), ^{
[impl.queue addOperationWithBlock:^{
// Execute the runnable only if the executor is not shutting down, and the runnable isn't
// canceled.
// Warning: This block should reference only Obj-C objects, and never C++ objects.
if (!impl.shuttingDown && !wrapper->_canceled->Get()) {
wrapper->_runnable();
}
}];
});
return std::shared_ptr<api::Cancelable>(cancelable);
}
void ScheduledExecutor::Execute(Runnable &&runnable) {
DoSubmit(std::move(runnable));
}
bool ScheduledExecutor::DoSubmit(Runnable &&runnable) {
if (impl_.shuttingDown) {
return false;
}
// Submit the runnable to the queue.
Runnable local_runnable = std::move(runnable);
[impl_.queue addOperationWithBlock:^{
local_runnable();
}];
return true;
}
void ScheduledExecutor::Shutdown(std::int64_t timeout_millis) {
// Prevent new/delayed operations from being queued/executed.
impl_.shuttingDown = YES;
// Block until either (a) all currently executing operations finish, or (b) the timeout expires.
dispatch_group_t group = dispatch_group_create();
dispatch_group_async(group, dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0), ^{
[impl_.queue waitUntilAllOperationsAreFinished];
});
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, timeout_millis * NSEC_PER_MSEC));
}
} // namespace ios
} // namespace nearby
} // namespace location
@@ -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_
@@ -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
@@ -0,0 +1,74 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <CoreBluetooth/CoreBluetooth.h>
#import <Foundation/Foundation.h>
#include <set>
#include <string>
#include "absl/container/flat_hash_map.h"
#include "absl/strings/string_view.h"
#include "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<CBUUID *> *CBUUIDSetFromBluetoothUUIDStringSet(const std::set<std::string> &bluetoothUUIDSet);
/// Converts an NSSet of CBUUID to a C++ set of strings (representing Bluetooth UUIDs).
std::set<std::string> BluetoothUUIDStringSetFromCBUUIDSet(NSSet<CBUUID *> *bluetoothUUIDSet);
/// Converts a C++ TxtRecord to an Obj-C TxtRecord.
NSDictionary<NSString *, NSData *> *NSDictionaryFromCppTxtRecords(
const absl::flat_hash_map<std::string, std::string> &txt_records);
/// Converts an Obj-C TxtRecord to a C++ TxtRecord.
absl::flat_hash_map<std::string, std::string> AbslHashMapFromObjCTxtRecords(
NSDictionary<NSString *, NSData *> *txtRecords);
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -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<CBUUID*>* CBUUIDSetFromBluetoothUUIDStringSet(const std::set<std::string>& bluetoothUUIDSet) {
NSMutableSet<CBUUID*>* objcBluetoothUUIDSet = [NSMutableSet set];
for (std::set<std::string>::const_iterator it = bluetoothUUIDSet.begin();
it != bluetoothUUIDSet.end(); ++it) {
[objcBluetoothUUIDSet addObject:CBUUIDFromBluetoothUUIDString(*it)];
}
return objcBluetoothUUIDSet;
}
std::set<std::string> BluetoothUUIDStringSetFromCBUUIDSet(NSSet<CBUUID*>* bluetoothUUIDSet) {
std::set<std::string> cppBluetoothUUIDSet;
for (CBUUID* bluetoothUUID in bluetoothUUIDSet) {
cppBluetoothUUIDSet.insert(BluetoothUUIDStringFromCBUUID(bluetoothUUID));
}
return cppBluetoothUUIDSet;
}
NSDictionary<NSString*, NSData*>* NSDictionaryFromCppTxtRecords(
const absl::flat_hash_map<std::string, std::string>& txt_records) {
NSMutableArray<NSString*>* keyArray = [[NSMutableArray alloc] init];
NSMutableArray<NSData*>* valueArray = [[NSMutableArray alloc] init];
for (auto it = txt_records.begin(); it != txt_records.end(); it++) {
NSString* key = @(it->first.c_str());
NSString* value = @(it->second.c_str());
[keyArray addObject:key];
[valueArray addObject:[value dataUsingEncoding:NSUTF8StringEncoding]];
}
NSDictionary<NSString*, NSData*>* dict = [[NSDictionary alloc] initWithObjects:valueArray
forKeys:keyArray];
return dict;
}
absl::flat_hash_map<std::string, std::string> AbslHashMapFromObjCTxtRecords(
NSDictionary<NSString*, NSData*>* txtRecords) {
absl::flat_hash_map<std::string, std::string> txt_record;
for (NSString* key in txtRecords) {
NSString* value = [[NSString alloc] initWithData:[txtRecords objectForKey:key]
encoding:NSUTF8StringEncoding];
txt_record.insert({CppStringFromObjCString(key), CppStringFromObjCString(value)});
}
return txt_record;
}
} // namespace nearby
} // namespace location
NS_ASSUME_NONNULL_END
@@ -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 <Foundation/Foundation.h>
#include <string>
#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<ByteArray> Read(std::int64_t size) override;
Exception Close() override;
GNCMConnectionHandlers* GetConnectionHandlers() { return connectionHandlers_; }
private:
GNCMConnectionHandlers* connectionHandlers_;
NSMutableArray<NSData*>* newDataPackets_;
NSMutableData* accumulatedData_;
NSCondition* condition_;
};
/** OutputStream that writes to GNCMConnection. */
class WifiLanOutputStream : public OutputStream {
public:
explicit WifiLanOutputStream(id<GNCMConnection> connection)
: connection_(connection), condition_([[NSCondition alloc] init]) {}
~WifiLanOutputStream() override;
Exception Write(const ByteArray& data) override;
Exception Flush() override;
Exception Close() override;
private:
id<GNCMConnection> connection_;
NSCondition* condition_;
};
/** Concrete WifiLanSocket implementation. */
class WifiLanSocket : public api::WifiLanSocket {
public:
WifiLanSocket() = default;
explicit WifiLanSocket(id<GNCMConnection> connection);
~WifiLanSocket() override;
// api::WifiLanSocket:
InputStream& GetInputStream() override { return *input_stream_; }
OutputStream& GetOutputStream() override { return *output_stream_; }
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
bool IsClosed() const ABSL_LOCKS_EXCLUDED(mutex_);
private:
void DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable absl::Mutex mutex_;
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
std::unique_ptr<WifiLanInputStream> input_stream_;
std::unique_ptr<WifiLanOutputStream> output_stream_;
};
/** Concrete WifiLanServerSocket implementation. */
class WifiLanServerSocket : public api::WifiLanServerSocket {
public:
static std::string GetName(const std::string& ip_address, int port);
~WifiLanServerSocket() override;
// api::WifiLanServerSocket:
std::string GetIPAddress() const override ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
return ip_address_;
}
void SetIPAddress(const std::string& ip_address) ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
ip_address_ = ip_address;
}
int GetPort() const override ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
return port_;
}
void SetPort(int port) ABSL_LOCKS_EXCLUDED(mutex_) {
absl::MutexLock lock(&mutex_);
port_ = port;
}
std::unique_ptr<api::WifiLanSocket> Accept() override ABSL_LOCKS_EXCLUDED(mutex_);
Exception Close() override ABSL_LOCKS_EXCLUDED(mutex_);
bool Connect(std::unique_ptr<WifiLanSocket> socket) ABSL_LOCKS_EXCLUDED(mutex_);
void SetCloseNotifier(std::function<void()> notifier) ABSL_LOCKS_EXCLUDED(mutex_);
private:
Exception DoClose() ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
mutable absl::Mutex mutex_;
std::string ip_address_ ABSL_GUARDED_BY(mutex_);
int port_ ABSL_GUARDED_BY(mutex_);
absl::CondVar cond_;
absl::flat_hash_set<std::unique_ptr<WifiLanSocket>> pending_sockets_ ABSL_GUARDED_BY(mutex_);
std::function<void()> close_notifier_ ABSL_GUARDED_BY(mutex_);
bool closed_ ABSL_GUARDED_BY(mutex_) = false;
};
/** Concrete WifiLanMedium implementation. */
class WifiLanMedium : public api::WifiLanMedium {
public:
WifiLanMedium() = default;
~WifiLanMedium() override;
WifiLanMedium(const WifiLanMedium&) = delete;
WifiLanMedium& operator=(const WifiLanMedium&) = delete;
// api::WifiLanMedium:
bool StartAdvertising(const NsdServiceInfo& nsd_service_info) override
ABSL_LOCKS_EXCLUDED(mutex_);
bool StopAdvertising(const NsdServiceInfo& nsd_service_info) override ABSL_LOCKS_EXCLUDED(mutex_);
bool StartDiscovery(const std::string& service_type, DiscoveredServiceCallback callback) override
ABSL_LOCKS_EXCLUDED(mutex_);
bool StopDiscovery(const std::string& service_type) override ABSL_LOCKS_EXCLUDED(mutex_);
std::unique_ptr<api::WifiLanSocket> ConnectToService(
const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) override;
std::unique_ptr<api::WifiLanSocket> ConnectToService(
const std::string& ip_address, int port, CancellationFlag* cancellation_flag) override;
std::unique_ptr<api::WifiLanServerSocket> ListenForService(int port) override
ABSL_LOCKS_EXCLUDED(mutex_);
private:
struct AdvertisingInfo {
bool Empty() const { return services.empty(); }
void Clear() { services.clear(); }
void Add(const std::string& service_type, GNCMBonjourService* service) {
services.insert({service_type, service});
}
void Remove(const std::string& service_type) { services.erase(service_type); }
bool Existed(const std::string& service_type) const { return services.contains(service_type); }
absl::flat_hash_map<std::string, GNCMBonjourService*> services;
};
struct DiscoveringInfo {
bool Empty() const { return services.empty(); }
void Clear() { services.clear(); }
void Add(const std::string& service_type, GNCMBonjourBrowser* browser) {
services.insert({service_type, browser});
}
void Remove(const std::string& service_type) { services.erase(service_type); }
bool Existed(const std::string& service_type) const { return services.contains(service_type); }
absl::flat_hash_map<std::string, GNCMBonjourBrowser*> services;
};
std::string GetFakeIPAddress() const;
int GetFakePort() const;
absl::Mutex mutex_;
AdvertisingInfo advertising_info_ ABSL_GUARDED_BY(mutex_);
int requesting_port_ = 0;
DiscoveringInfo discovering_info_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<std::string, WifiLanServerSocket*> server_sockets_ ABSL_GUARDED_BY(mutex_);
absl::flat_hash_map<std::string, GNCMConnectionRequester> connection_requesters_
ABSL_GUARDED_BY(mutex_);
};
} // namespace ios
} // namespace nearby
} // namespace location
#endif // PLATFORM_IMPL_IOS_WIFI_LAN_H_
@@ -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 <memory>
#include <string>
#include <utility>
#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<ByteArray> WifiLanInputStream::Read(std::int64_t size) {
// Block until either (a) the connection has been closed, or (b) enough data to return.
NSData* dataToReturn;
[condition_ lock];
while (true) {
// Check if the stream has been closed or severed.
if (!newDataPackets_) break;
if (newDataPackets_.count > 0) {
// Add the packet data to the accumulated data.
for (NSData* data in newDataPackets_) {
if (data.length > 0) {
[accumulatedData_ appendData:data];
}
}
[newDataPackets_ removeAllObjects];
}
if ((size == -1) && (accumulatedData_.length > 0)) {
// Return all of the data.
dataToReturn = accumulatedData_;
accumulatedData_ = [NSMutableData data];
break;
} else if (accumulatedData_.length > 0) {
// Return up to |size| bytes of the data.
std::int64_t sizeToReturn = accumulatedData_.length < size ? accumulatedData_.length : size;
NSRange range = NSMakeRange(0, (NSUInteger)sizeToReturn);
dataToReturn = [accumulatedData_ subdataWithRange:range];
[accumulatedData_ replaceBytesInRange:range withBytes:nil length:0];
break;
}
[condition_ wait];
}
[condition_ unlock];
if (dataToReturn) {
GTMLoggerInfo(@"[NEARBY] Input stream: Received data of size: %lu",
(unsigned long)dataToReturn.length);
return ExceptionOr<ByteArray>(ByteArrayFromNSData(dataToReturn));
} else {
return ExceptionOr<ByteArray>{Exception::kIo};
}
}
Exception WifiLanInputStream::Close() {
// Unblock pending read operation.
[condition_ lock];
newDataPackets_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** WifiLanOutputStream implementation. */
WifiLanOutputStream::~WifiLanOutputStream() {
NSCAssert(!connection_, @"WifiLanOutputStream not closed before destruction");
}
Exception WifiLanOutputStream::Write(const ByteArray& data) {
[condition_ lock];
GTMLoggerDebug(@"[NEARBY] Sending data of size: %lu",
(unsigned long)NSDataFromByteArray(data).length);
NSMutableData* packet = [NSMutableData dataWithData:NSDataFromByteArray(data)];
// Send the data, blocking until the completion handler is called.
__block GNCMPayloadResult sendResult = GNCMPayloadResultFailure;
__block bool isComplete = NO;
NSCondition* condition = condition_; // don't capture |this| in completion
// Check if connection_ is nil, then just don't wait and return as failure.
if (connection_ != nil) {
[connection_ sendData:packet
progressHandler:^(size_t count) {
}
completion:^(GNCMPayloadResult result) {
// Make sure we haven't already reported completion before. This prevents a crash
// where we try leaving a dispatch group more times than we entered it.
// b/79095653.
if (isComplete) {
return;
}
isComplete = YES;
sendResult = result;
[condition lock];
[condition signal];
[condition unlock];
}];
[condition_ wait];
[condition_ unlock];
} else {
sendResult = GNCMPayloadResultFailure;
[condition_ unlock];
}
if (sendResult == GNCMPayloadResultSuccess) {
return {Exception::kSuccess};
} else {
return {Exception::kIo};
}
}
Exception WifiLanOutputStream::Flush() {
// The Write() function block until the data is received by the remote endpoint, so there's
// nothing to do here.
return {Exception::kSuccess};
}
Exception WifiLanOutputStream::Close() {
// Unblock pending write operation.
[condition_ lock];
connection_ = nil;
[condition_ signal];
[condition_ unlock];
return {Exception::kSuccess};
}
/** WifiLanSocket implementation. */
WifiLanSocket::WifiLanSocket(id<GNCMConnection> connection)
: input_stream_(new WifiLanInputStream()),
output_stream_(new WifiLanOutputStream(connection)) {}
WifiLanSocket::~WifiLanSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
bool WifiLanSocket::IsClosed() const {
absl::MutexLock lock(&mutex_);
return closed_;
}
Exception WifiLanSocket::Close() {
absl::MutexLock lock(&mutex_);
DoClose();
return {Exception::kSuccess};
}
void WifiLanSocket::DoClose() {
if (!closed_) {
input_stream_->Close();
output_stream_->Close();
closed_ = true;
}
}
/** WifiLanServerSocket implementation. */
std::string WifiLanServerSocket::GetName(const std::string& ip_address, int port) {
std::string dot_delimited_string;
if (!ip_address.empty()) {
for (auto byte : ip_address) {
if (!dot_delimited_string.empty()) absl::StrAppend(&dot_delimited_string, ".");
absl::StrAppend(&dot_delimited_string, absl::StrFormat("%d", byte));
}
}
std::string out = absl::StrCat(dot_delimited_string, ":", port);
return out;
}
WifiLanServerSocket::~WifiLanServerSocket() {
absl::MutexLock lock(&mutex_);
DoClose();
}
std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() {
absl::MutexLock lock(&mutex_);
while (!closed_ && pending_sockets_.empty()) {
cond_.Wait(&mutex_);
}
// Return early if closed.
if (closed_) return {};
auto remote_socket = std::move(pending_sockets_.extract(pending_sockets_.begin()).value());
return std::move(remote_socket);
}
bool WifiLanServerSocket::Connect(std::unique_ptr<WifiLanSocket> socket) {
absl::MutexLock lock(&mutex_);
if (closed_) {
return false;
}
// add client socket to the pending list
pending_sockets_.insert(std::move(socket));
cond_.SignalAll();
if (closed_) {
return false;
}
return true;
}
void WifiLanServerSocket::SetCloseNotifier(std::function<void()> notifier) {
absl::MutexLock lock(&mutex_);
close_notifier_ = std::move(notifier);
}
Exception WifiLanServerSocket::Close() {
absl::MutexLock lock(&mutex_);
return DoClose();
}
Exception WifiLanServerSocket::DoClose() {
bool should_notify = !closed_;
closed_ = true;
if (should_notify) {
cond_.SignalAll();
if (close_notifier_) {
auto notifier = std::move(close_notifier_);
mutex_.Unlock();
// Notifier may contain calls to public API, and may cause deadlock, if
// mutex_ is held during the call.
notifier();
mutex_.Lock();
}
}
return {Exception::kSuccess};
}
/** WifiLanMedium implementation. */
WifiLanMedium::~WifiLanMedium() {
advertising_info_.Clear();
discovering_info_.Clear();
}
bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) {
std::string service_type = nsd_service_info.GetServiceType();
NSString* serviceType = ObjCStringFromCppString(service_type);
{
absl::MutexLock lock(&mutex_);
if (advertising_info_.Existed(service_type)) {
GTMLoggerInfo(@"[NEARBY] WifiLan StartAdvertising: Can't start advertising because "
@"service_type=%@, has started already",
serviceType);
return false;
}
}
// Retrieve service name.
NSString* serviceName = ObjCStringFromCppString(nsd_service_info.GetServiceName());
// Retrieve TXTRecord and convert it to NSDictionary type.
NSDictionary<NSString*, NSData*>* TXTRecordData =
NSDictionaryFromCppTxtRecords(nsd_service_info.GetTxtRecords());
// Get ip address and port to retrieve the server_socket, if not, then return nil.
std::string ip_address = nsd_service_info.GetIPAddress();
int port = nsd_service_info.GetPort();
__block std::string socket_name = WifiLanServerSocket::GetName(ip_address, port);
GNCMBonjourService* service = [[GNCMBonjourService alloc]
initWithServiceName:serviceName
serviceType:serviceType
port:requesting_port_
TXTRecordData:TXTRecordData
endpointConnectedHandler:^GNCMConnectionHandlers*(id<GNCMConnection> connection) {
auto item = server_sockets_.find(socket_name);
WifiLanServerSocket* server_socket = item != server_sockets_.end() ? item->second : nullptr;
if (!server_socket) {
return nil;
}
auto socket = absl::make_unique<WifiLanSocket>(connection);
GNCMConnectionHandlers* connectionHandlers =
static_cast<WifiLanInputStream&>(socket->GetInputStream()).GetConnectionHandlers();
server_socket->Connect(std::move(socket));
return connectionHandlers;
}];
{
absl::MutexLock lock(&mutex_);
advertising_info_.Add(service_type, service);
}
return true;
}
bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) {
std::string service_type = nsd_service_info.GetServiceType();
{
absl::MutexLock lock(&mutex_);
if (!advertising_info_.Existed(service_type)) {
GTMLoggerInfo(@"[NEARBY] WifiLan StopAdvertising: Can't stop advertising because we never "
@"started advertising for service_type=%@",
ObjCStringFromCppString(service_type));
return false;
}
advertising_info_.Remove(service_type);
}
return true;
}
bool WifiLanMedium::StartDiscovery(const std::string& service_type,
DiscoveredServiceCallback callback) {
NSString* serviceType = ObjCStringFromCppString(service_type);
{
absl::MutexLock lock(&mutex_);
if (discovering_info_.Existed(service_type)) {
GTMLoggerInfo(@"[NEARBY] WifiLan StartAdvertising: Can't start discovery because "
@"service_type=%@, has started already",
serviceType);
return false;
}
}
GNCMBonjourBrowser* browser = [[GNCMBonjourBrowser alloc]
initWithServiceType:serviceType
endpointFoundHandler:^GNCMEndpointLostHandler(
NSString* endpointId, NSString* serviceType, NSString* serviceName,
NSDictionary<NSString*, NSData*>* _Nullable txtRecordData,
GNCMConnectionRequester requestConnection) {
__block NsdServiceInfo nsd_service_info = {};
nsd_service_info.SetServiceName(CppStringFromObjCString(serviceName));
nsd_service_info.SetServiceType(CppStringFromObjCString(serviceType));
// Set TXTRecord converted from NSDictionary to hash map.
if (txtRecordData != nil) {
auto txt_records = AbslHashMapFromObjCTxtRecords(txtRecordData);
nsd_service_info.SetTxtRecords(txt_records);
}
connection_requesters_.insert({CppStringFromObjCString(serviceType), requestConnection});
callback.service_discovered_cb(nsd_service_info);
return ^{
callback.service_lost_cb(nsd_service_info);
};
}];
{
absl::MutexLock lock(&mutex_);
discovering_info_.Add(service_type, browser);
}
return true;
}
bool WifiLanMedium::StopDiscovery(const std::string& service_type) {
{
absl::MutexLock lock(&mutex_);
if (!discovering_info_.Existed(service_type)) {
GTMLoggerInfo(@"[NEARBY] WifiLan StopDiscovery: Can't stop discovering because "
"we never started discovering.");
return false;
}
discovering_info_.Remove(service_type);
}
return true;
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) {
std::string service_type = remote_service_info.GetServiceType();
GTMLoggerInfo(@"[NEARBY] WifiLan ConnectToService, service_type=%@",
ObjCStringFromCppString(service_type));
GNCMConnectionRequester connection_requester = nil;
{
absl::MutexLock lock(&mutex_);
const auto& it = connection_requesters_.find(service_type);
if (it == connection_requesters_.end()) {
return {};
}
connection_requester = it->second;
}
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block std::unique_ptr<WifiLanSocket> socket;
if (connection_requester != nil) {
if (cancellation_flag->Cancelled()) {
GTMLoggerError(@"[NEARBY] WifiLan Connect: Has been cancelled: service_type=%@",
ObjCStringFromCppString(service_type));
dispatch_group_leave(group); // unblock
return {};
}
connection_requester(^(id<GNCMConnection> connection) {
// If the connection wasn't successfully established, return a NULL socket.
if (connection) {
socket = absl::make_unique<WifiLanSocket>(connection);
}
dispatch_group_leave(group); // unblock
return socket != nullptr ? static_cast<WifiLanInputStream&>(socket->GetInputStream())
.GetConnectionHandlers()
: nullptr;
});
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
return std::move(socket);
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const std::string& ip_address, int port, CancellationFlag* cancellation_flag) {
// Not implemented.
return {};
}
std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(int port) {
auto server_socket = std::make_unique<WifiLanServerSocket>();
// The fake ip address and port need to be set here since they can't be retrieved before
// StartAadvertising begins. Furthermore, NSNetService can't resolve ip address when finding
// service. Try to fake them and make it socket name as a key of server_sockets_ which acts
// the same socket binding.
server_socket->SetIPAddress(GetFakeIPAddress());
requesting_port_ = port;
server_socket->SetPort(requesting_port_ == 0 ? GetFakePort() : requesting_port_);
std::string socket_name =
WifiLanServerSocket::GetName(server_socket->GetIPAddress(), server_socket->GetPort());
server_socket->SetCloseNotifier([this, socket_name]() {
absl::MutexLock lock(&mutex_);
server_sockets_.erase(socket_name);
});
GTMLoggerInfo(@"[NEARBY] WifiLan Adding server socket, socket_name=%@",
ObjCStringFromCppString(socket_name));
absl::MutexLock lock(&mutex_);
server_sockets_.insert({socket_name, server_socket.get()});
return server_socket;
}
std::string WifiLanMedium::GetFakeIPAddress() const {
std::string ip_address;
ip_address.resize(4);
uint32_t raw_ip_addr = Prng().NextUint32();
ip_address[0] = static_cast<char>(raw_ip_addr >> 24);
ip_address[1] = static_cast<char>(raw_ip_addr >> 16);
ip_address[2] = static_cast<char>(raw_ip_addr >> 8);
ip_address[3] = static_cast<char>(raw_ip_addr >> 0);
return ip_address;
}
int WifiLanMedium::GetFakePort() const {
uint16_t port = Prng().NextUint32();
return port;
}
} // namespace ios
} // namespace nearby
} // namespace location
+30
View File
@@ -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",
],
)
@@ -0,0 +1,50 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
// Current version of socket protocol.
extern const int GNCSocketVersion;
#ifdef __cplusplus
extern "C" {
#endif
/// Generates a SHA-256 hash (32 bytes) from a data object.
NSData *_Nullable GNCSha256Data(NSData *data);
/// Generates a SHA-256 hash (32 bytes) from a string.
NSData *_Nullable GNCSha256String(NSString *string);
/// Generates an MD5 hash (16 bytes) from a data object.
NSData *_Nullable GNCMd5Data(NSData *data);
/// Generates an MD5 hash (16 bytes) from a string.
NSData *_Nullable GNCMd5String(NSString *string);
/// Base64-encode.
NSString *GNCBase64Encode(NSData *data);
/// Base64-decode.
NSData *GNCBase64Decode(NSString *string);
#ifdef __cplusplus
} // extern "C"
#endif
NS_ASSUME_NONNULL_END
@@ -0,0 +1,65 @@
#import "third_party/nearby_connections/cpp/platform/impl/ios/Source/Shared/GNCUtils.h"
#import <CommonCrypto/CommonDigest.h>
#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
+64
View File
@@ -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",
],
)
@@ -0,0 +1,52 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#include "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
@@ -0,0 +1,56 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#import "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<InputFile>(testURL);
XCTAssert(input_file != nullptr);
XCTAssertEqual(input_file->GetTotalSize(), 0);
ExceptionOr<ByteArray> read_result = input_file->Read(3);
XCTAssertFalse(read_result.ok());
}
- (void)testGetFilePath {
std::string cc_path("/foo/bar/test.ext");
NSString* objcPath = [NSString stringWithUTF8String:cc_path.c_str()];
NSURL *testURL = [NSURL URLWithString:objcPath];
auto input_file = std::make_unique<InputFile>(testURL);
XCTAssert(input_file != nullptr);
std::string input_file_path = input_file->GetFilePath();
XCTAssertTrue([objcPath isEqualToString:[NSString stringWithUTF8String:input_file_path.c_str()]]);
}
@end
@@ -0,0 +1,114 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#include "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<MultiThreadExecutor>)executor {
std::unique_ptr<MultiThreadExecutor> executor =
ImplementationPlatform::CreateMultiThreadExecutor(4);
XCTAssert(executor != nullptr);
return executor;
}
// Tests that the executor executes runnables as expected.
- (void)testExecute {
std::unique_ptr<MultiThreadExecutor> executor([self executor]);
// Execute two runnables that increment the counter.
const int kIncrements = 13;
Runnable incrementer = [self]() { self.counter++; };
for (int i = 0; i < kIncrements; i++) {
executor->Execute(std::move(incrementer));
}
// Check that the counter has the expected value after giving the runnables time to run.
[NSThread sleepForTimeInterval:0.01];
XCTAssertEqual(self.counter, kIncrements);
}
// Tests that the executor submits runnables as expected.
- (void)testSubmit {
std::unique_ptr<MultiThreadExecutor> executor([self executor]);
// Submit two runnables that increment the counter.
const int kIncrements = 13;
Runnable incrementer = [self]() { self.counter++; };
for (int i = 0; i < kIncrements; i++) {
executor->DoSubmit(std::move(incrementer));
}
// Check that the counter has the expected value after giving the runnables time to run.
[NSThread sleepForTimeInterval:0.01];
XCTAssertEqual(self.counter, kIncrements);
}
// Tests that fails to submit when the executor is shut down.
- (void)testFailtoSubmitAfterShutdown {
std::unique_ptr<MultiThreadExecutor> executor([self executor]);
executor->Shutdown();
XCTAssertFalse(executor->DoSubmit([self]() { self.counter++; }));
// Check that the counter has the expected value after giving the runnables time to run.
[NSThread sleepForTimeInterval:0.01];
XCTAssertEqual(self.counter, 0);
}
// Tests that when the executor is shut down, it waits for pending operations to finish, and no new
// operations will be executed.
- (void)testShutdownToAllowExistingTaskComplete {
std::unique_ptr<MultiThreadExecutor> executor([self executor]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
const int kRunnableCount = 1000;
for (int i = 0; i < kRunnableCount; i++) {
executor->Execute([self]() { self.counter++; });
}
executor->Shutdown();
executor->Execute([self]() { self.otherCounter++; });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{
XCTAssertLessThanOrEqual(self.counter, kRunnableCount);
XCTAssertEqual(self.otherCounter, 0);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:0.5 handler:nil];
}
@end
@@ -0,0 +1,139 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#include "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<ScheduledExecutor>)executor {
std::unique_ptr<ScheduledExecutor> executor = ImplementationPlatform::CreateScheduledExecutor();
XCTAssert(executor != nullptr);
return executor;
}
// Tests that the executor schedules operation at the specified time.
- (void)testScheduling {
std::unique_ptr<ScheduledExecutor> executor([self executor]);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
Runnable incrementer = [self]() { self.counter++; };
void (^checkCounter)(int, NSTimeInterval, dispatch_block_t) =
^(int expectedCount, NSTimeInterval delay, dispatch_block_t finalBlock) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
XCTAssertEqual(self.counter, expectedCount);
finalBlock();
});
};
// Schedule two runnables that increment the counter, at 0.4 and 0.8 seconds.
executor->Schedule(std::move(incrementer), absl::Seconds(0.4));
executor->Schedule(std::move(incrementer), absl::Seconds(0.8));
// Check that the counter contains the expected values at 0.2, 0.6, and 1.0 seconds.
checkCounter(0, 0.2, ^{});
checkCounter(1, 0.6, ^{});
checkCounter(2, 1.0, ^{ [expectation fulfill]; });
[self waitForExpectationsWithTimeout:1.2 handler:nil];
}
// Tests that fails to schedule when the executor is shut down.
- (void)testFailtoScheduleAfterShutdown {
std::unique_ptr<ScheduledExecutor> executor([self executor]);
executor->Shutdown();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
const NSTimeInterval delay = 0.1;
executor->Schedule([self]() { self.counter++; }, absl::Milliseconds(delay));
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * 2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 0);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:delay * 5 handler:nil];
}
// Tests that fails to cancel when the executor is shut down.
- (void)testFailToCancelAfterShutdown {
std::unique_ptr<ScheduledExecutor> executor([self executor]);
executor->Shutdown();
auto cancelable = executor->Schedule([self]() { self.counter++; }, absl::Seconds(0.1));
XCTAssert(cancelable.get() == nullptr);
}
// Tests that shutting down an existing task fails to complete.
- (void)testShutdownToFailExistingTask {
std::unique_ptr<ScheduledExecutor> executor([self executor]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
const NSTimeInterval delay = 0.1;
executor->Schedule([self]() { self.counter++; }, absl::Milliseconds(delay));
executor->Shutdown();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay * 2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 0);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:delay * 5 handler:nil];
}
// Tests that a canceled runnable doesn't run.
- (void)testCancelable {
std::unique_ptr<ScheduledExecutor> executor([self executor]);
// This runnable should run but not increment the counter because it sleeps for longer than the
// cancel below.
const NSTimeInterval delay = 0.1;
auto cancelable = executor->Schedule([self]() { self.counter++; }, absl::Seconds(delay));
XCTAssert(cancelable.get() != nullptr);
// Cancel the runnable.
cancelable->Cancel();
// Wait for the time interval to pass and verify that it didn't run.
[NSThread sleepForTimeInterval:delay * 1.1];
XCTAssertEqual(self.counter, 0);
}
@end
@@ -0,0 +1,95 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#include "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<SingleThreadExecutor>)executor {
std::unique_ptr<SingleThreadExecutor> executor =
ImplementationPlatform::CreateSingleThreadExecutor();
XCTAssert(executor != nullptr);
return executor;
}
// Tests that the executor executes runnables as expected.
- (void)testRunnables {
std::unique_ptr<SingleThreadExecutor> executor([self executor]);
Runnable incrementer = [self]() { self.counter++; };
// Schedule two runnables that increment the counter.
executor->Execute(std::move(incrementer));
executor->Execute(std::move(incrementer));
// Check that the counter has the expected value after a moment.
[NSThread sleepForTimeInterval:0.01];
XCTAssertEqual(self.counter, 2);
}
// Tests that fails to execute when the executor is shut down.
- (void)testShutdownBeforeInvokeTask {
std::unique_ptr<SingleThreadExecutor> executor([self executor]);
executor->Shutdown();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
executor->Execute([self]() { self.counter++; });
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 0);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:0.5 handler:nil];
}
// Tests that shutting down an existing task allows to complete.
- (void)testShutdownToAllowExistingTaskComplete {
std::unique_ptr<SingleThreadExecutor> executor([self executor]);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_TARGET_QUEUE_DEFAULT, 0);
XCTestExpectation *expectation = [self expectationWithDescription:@"finished"];
executor->Execute([self]() { self.counter++; });
executor->Shutdown();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), queue, ^{
XCTAssertEqual(self.counter, 1);
[expectation fulfill];
});
[self waitForExpectationsWithTimeout:0.5 handler:nil];
}
@end
@@ -0,0 +1,46 @@
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#include "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
-1
View File
@@ -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 = [
+1 -1
View File
@@ -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__",
],