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