Files
nearby/internal/platform/implementation/apple/wifi_lan.mm
T
2025-02-10 22:47:21 -08:00

247 lines
9.5 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 "internal/platform/implementation/apple/wifi_lan.h"
#import <Foundation/Foundation.h>
#include <memory>
#include <string>
#include <utility>
#import "internal/platform/implementation/apple/Mediums/NWFramework/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/NWFramework/GNCNWFramework.h"
#import "internal/platform/implementation/apple/Mediums/NWFramework/GNCNWFrameworkServerSocket.h"
#import "internal/platform/implementation/apple/Mediums/NWFramework/GNCNWFrameworkSocket.h"
#import "GoogleToolboxForMac/GTMLogger.h"
namespace nearby {
namespace apple {
#pragma mark - WifiLanInputStream
WifiLanInputStream::WifiLanInputStream(GNCNWFrameworkSocket* socket) : socket_(socket) {}
ExceptionOr<ByteArray> WifiLanInputStream::Read(std::int64_t size) {
NSError* error = nil;
NSData* data = [socket_ readMaxLength:size error:&error];
if (data == nil) {
GTMLoggerError(@"Error reading socket: %@", error);
return {Exception::kIo};
}
return ExceptionOr<ByteArray>{ByteArray((const char*)data.bytes, data.length)};
}
Exception WifiLanInputStream::Close() {
// The input stream reads directly from the connection. It can not be closed without closing the
// connection itself. A call to `WifiLanSocket::Close` will close the connection.
return {Exception::kSuccess};
}
#pragma mark - WifiLanOutputStream
WifiLanOutputStream::WifiLanOutputStream(GNCNWFrameworkSocket* socket) : socket_(socket) {}
Exception WifiLanOutputStream::Write(const ByteArray& data) {
NSError* error = nil;
BOOL result = [socket_ write:[NSData dataWithBytes:data.data() length:data.size()] error:&error];
if (!result) {
GTMLoggerError(@"Error writing socket: %@", error);
return {Exception::kIo};
}
return {Exception::kSuccess};
}
Exception WifiLanOutputStream::Flush() {
// Write blocks until the data has successfully been written/received, so no more work is needed
// to flush.
return {Exception::kSuccess};
}
Exception WifiLanOutputStream::Close() {
// The output stream writes directly to the connection. It can not be closed without closing the
// connection itself. A call to `WifiLanSocket::Close` will close the connection.
return {Exception::kSuccess};
}
#pragma mark - WifiLanSocket
WifiLanSocket::WifiLanSocket(GNCNWFrameworkSocket* socket)
: socket_(socket),
input_stream_(std::make_unique<WifiLanInputStream>(socket)),
output_stream_(std::make_unique<WifiLanOutputStream>(socket)) {}
InputStream& WifiLanSocket::GetInputStream() { return *input_stream_; }
OutputStream& WifiLanSocket::GetOutputStream() { return *output_stream_; }
Exception WifiLanSocket::Close() {
[socket_ close];
return {Exception::kSuccess};
}
#pragma mark - WifiLanServerSocket
WifiLanServerSocket::WifiLanServerSocket(GNCNWFrameworkServerSocket* server_socket)
: server_socket_(server_socket) {}
std::string WifiLanServerSocket::GetIPAddress() const {
NSData* addressData = server_socket_.ipAddress.binaryRepresentation;
return std::string((char*)addressData.bytes, addressData.length);
}
int WifiLanServerSocket::GetPort() const { return server_socket_.port; }
std::unique_ptr<api::WifiLanSocket> WifiLanServerSocket::Accept() {
NSError* error = nil;
GNCNWFrameworkSocket* socket = [server_socket_ acceptWithError:&error];
if (socket != nil) {
return std::make_unique<WifiLanSocket>(socket);
}
if (error != nil) {
GTMLoggerError(@"Error accepting socket: %@", error);
}
return nil;
}
Exception WifiLanServerSocket::Close() {
[server_socket_ close];
return {Exception::kSuccess};
}
#pragma mark - WifiLanMedium
WifiLanMedium::WifiLanMedium() : medium_([[GNCNWFramework alloc] init]) {}
bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) {
NSInteger port = nsd_service_info.GetPort();
NSString* serviceName = @(nsd_service_info.GetServiceName().c_str());
NSString* serviceType = @(nsd_service_info.GetServiceType().c_str());
NSMutableDictionary<NSString*, NSString*>* txtRecords = [[NSMutableDictionary alloc] init];
for (const auto& record : nsd_service_info.GetTxtRecords()) {
[txtRecords setObject:@(record.second.c_str()) forKey:@(record.first.c_str())];
}
[medium_ startAdvertisingPort:port
serviceName:serviceName
serviceType:serviceType
txtRecords:txtRecords];
return true;
}
bool WifiLanMedium::StopAdvertising(const NsdServiceInfo& nsd_service_info) {
NSInteger port = nsd_service_info.GetPort();
[medium_ stopAdvertisingPort:port];
return true;
}
bool WifiLanMedium::StartDiscovery(const std::string& service_type,
DiscoveredServiceCallback callback) {
__block NSString* serviceType = @(service_type.c_str());
__block DiscoveredServiceCallback client_callback = std::move(callback);
// Set `includePeerToPeer` to YES to support peer-to-peer connections for Apple devices.
NSError* error = nil;
BOOL result = [medium_ startDiscoveryForServiceType:serviceType
includePeerToPeer:YES
serviceFoundHandler:^(NSString* name, NSDictionary<NSString*, NSString*>* txtRecords) {
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceType([serviceType UTF8String]);
nsd_service_info.SetServiceName([name UTF8String]);
[txtRecords
enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info](
NSString* key, NSString* val, BOOL* stop) {
nsd_service_info->SetTxtRecord([key UTF8String], [val UTF8String]);
}];
client_callback.service_discovered_cb(nsd_service_info);
}
serviceLostHandler:^(NSString* name, NSDictionary<NSString*, NSString*>* txtRecords) {
NsdServiceInfo nsd_service_info;
nsd_service_info.SetServiceType([serviceType UTF8String]);
nsd_service_info.SetServiceName([name UTF8String]);
[txtRecords
enumerateKeysAndObjectsUsingBlock:[nsd_service_info = &nsd_service_info](
NSString* key, NSString* val, BOOL* stop) {
nsd_service_info->SetTxtRecord([key UTF8String], [val UTF8String]);
}];
client_callback.service_lost_cb(nsd_service_info);
}
error:&error];
if (error != nil) {
GTMLoggerError(@"Error starting discovery for service type<%@>: %@", serviceType, error);
}
return result;
}
bool WifiLanMedium::StopDiscovery(const std::string& service_type) {
NSString* serviceType = @(service_type.c_str());
[medium_ stopDiscoveryForServiceType:serviceType];
return true;
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const NsdServiceInfo& remote_service_info, CancellationFlag* cancellation_flag) {
// Set `includePeerToPeer` to YES to support peer-to-peer connections for Apple devices.
NSError* error = nil;
NSString* serviceName = @(remote_service_info.GetServiceName().c_str());
NSString* serviceType = @(remote_service_info.GetServiceType().c_str());
GNCNWFrameworkSocket* socket = [medium_ connectToServiceName:serviceName
serviceType:serviceType
includePeerToPeer:YES
error:&error];
if (socket != nil) {
return std::make_unique<WifiLanSocket>(socket);
}
if (error != nil) {
GTMLoggerError(@"Error connecting to service name<%@> type<%@>: %@", serviceName, serviceType,
error);
}
return nil;
}
std::unique_ptr<api::WifiLanSocket> WifiLanMedium::ConnectToService(
const std::string& ip_address, int port, CancellationFlag* cancellation_flag) {
NSError* error = nil;
if (ip_address.size() != 4) {
GTMLoggerError(@"Error IP address must be 4 bytes, but is %lu bytes", ip_address.size());
return nil;
}
NSData* hostData = [NSData dataWithBytes:ip_address.data() length:ip_address.size()];
GNCIPv4Address* host = [GNCIPv4Address addressFromData:hostData];
GNCNWFrameworkSocket* socket = [medium_ connectToHost:host port:port error:&error];
if (socket != nil) {
return std::make_unique<WifiLanSocket>(socket);
}
if (error != nil) {
GTMLoggerError(@"Error connecting to %@:%d: %@", host, port, error);
}
return nil;
}
std::unique_ptr<api::WifiLanServerSocket> WifiLanMedium::ListenForService(int port) {
NSError* error = nil;
// Set `includePeerToPeer` to YES to support peer-to-peer connections for Apple devices.
GNCNWFrameworkServerSocket* serverSocket = [medium_ listenForServiceOnPort:port
includePeerToPeer:YES
error:&error];
if (serverSocket != nil) {
return std::make_unique<WifiLanServerSocket>(serverSocket);
}
if (error != nil) {
GTMLoggerError(@"Error listening for service: %@", error);
}
return nil;
}
} // namespace apple
} // namespace nearby