Files
nearby/cpp/platform/impl/ios/Source/Internal/GNCDiscoverer.mm
T
2021-11-06 09:45:54 -07:00

395 lines
16 KiB
Plaintext

// 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