diff --git a/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h b/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h index c0e0a1f8..a87e0f3b 100644 --- a/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h +++ b/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h @@ -19,6 +19,7 @@ NS_ASSUME_NONNULL_BEGIN @class GNCIPv4Address; @class GNCHotspotSocket; +@class GNCNWFramework; @class GNCNWFrameworkSocket; /** @@ -37,6 +38,16 @@ NS_ASSUME_NONNULL_BEGIN */ - (instancetype)initWithQueue:(dispatch_queue_t)queue; +/** + * Initializes the Hotspot medium with the specified queue and network framework. + * + * @param queue The queue to use for all internal operations. + * @param nwFramework The network framework to use for connections. + * @return An initialized Hotspot medium. + */ +- (instancetype)initWithQueue:(dispatch_queue_t)queue + nwFramework:(GNCNWFramework *)nwFramework NS_DESIGNATED_INITIALIZER; + /** * Connects to a Wifi Hotspot with the specified SSID and password. * diff --git a/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.m b/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.m index ca6e4131..c3681514 100644 --- a/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.m +++ b/internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.m @@ -27,6 +27,7 @@ #import "internal/platform/implementation/apple/Log/GNCLogger.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWConnectionImpl.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkError.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h" @@ -55,6 +56,7 @@ static const UInt8 kConnectionToHostTimeoutInSeconds = 10; @implementation GNCHotspotMedium { dispatch_queue_t _hotspot_queue; + GNCNWFramework *_nwFramework; } - (instancetype)init { @@ -62,10 +64,15 @@ static const UInt8 kConnectionToHostTimeoutInSeconds = 10; } - (instancetype)initWithQueue:(dispatch_queue_t)queue { + return [self initWithQueue:queue nwFramework:[[GNCNWFramework alloc] init]]; +} + +- (instancetype)initWithQueue:(dispatch_queue_t)queue nwFramework:(GNCNWFramework *)nwFramework { self = [super init]; if (self) { _hotspot_queue = queue; _locationManager = [[CLLocationManager alloc] init]; + _nwFramework = nwFramework; } return self; } @@ -238,90 +245,12 @@ static const UInt8 kConnectionToHostTimeoutInSeconds = 10; return nil; } - nw_endpoint_t endpoint = - nw_endpoint_create_host(host.dottedRepresentation.UTF8String, @(port).stringValue.UTF8String); - return [self connectToEndpoint:endpoint - includePeerToPeer:(BOOL)YES - cancelSource:cancelSource - error:error]; -} - -- (nullable GNCNWFrameworkSocket *)connectToEndpoint:(nw_endpoint_t)endpoint - includePeerToPeer:(BOOL)includePeerToPeer - cancelSource:(nullable dispatch_source_t)cancelSource - error:(NSError *_Nullable *_Nullable)error { - GNCLoggerInfo(@"connectToEndpoint: %@ includePeerToPeer: %d", endpoint.debugDescription, - includePeerToPeer); - - dispatch_semaphore_t semaphore_internal = dispatch_semaphore_create(0); - __block nw_connection_state_t blockResult = nw_connection_state_invalid; - __block NSError *blockError = nil; - - nw_parameters_t parameters = - nw_parameters_create_secure_tcp(/*tls*/ NW_PARAMETERS_DISABLE_PROTOCOL, - /*tcp*/ NW_PARAMETERS_DEFAULT_CONFIGURATION); - - nw_parameters_set_include_peer_to_peer(parameters, includePeerToPeer); - nw_connection_t connection = nw_connection_create(endpoint, parameters); - - nw_connection_set_queue(connection, _hotspot_queue); - - nw_connection_set_state_changed_handler( - connection, ^(nw_connection_state_t state, nw_error_t error) { - GNCLoggerDebug(@"connectToEndpoint state changed to: %d", state); - - // Ignore the preparing state and waiting state, because it is not a final state. - if ((state != nw_connection_state_preparing) && (state != nw_connection_state_waiting)) { - blockResult = state; - if (error != nil) { - GNCLoggerError(@"connectToEndpoint Error: %@", error.debugDescription); - blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(error); - } - dispatch_semaphore_signal(semaphore_internal); - } - }); - - if (cancelSource != nil) { - dispatch_source_set_event_handler(cancelSource, ^{ - GNCLoggerWarning(@"connectToEndpoint is cancelled."); - nw_connection_cancel(connection); - dispatch_source_cancel(cancelSource); - }); - } - - nw_connection_start(connection); - dispatch_time_t timeout = - dispatch_time(DISPATCH_TIME_NOW, kConnectionToHostTimeoutInSeconds * NSEC_PER_SEC); - if (dispatch_semaphore_wait(semaphore_internal, timeout) != 0) { - GNCLoggerError(@"Connecting to %@ timeout in %d seconds", endpoint.debugDescription, - kConnectionToHostTimeoutInSeconds); - nw_connection_set_state_changed_handler(connection, nil); // Prevent callback issues - nw_connection_cancel(connection); - if (error != nil) { - *error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain - code:GNCNWFrameworkErrorTimedOut - userInfo:nil]; - } - return nil; - } - - if (error != nil) { - *error = blockError; - } - - switch (blockResult) { - case nw_connection_state_invalid: - case nw_connection_state_waiting: - case nw_connection_state_preparing: - case nw_connection_state_failed: - case nw_connection_state_cancelled: - GNCLoggerError(@"connectToEndpoint failed with result: %d", blockResult); - return nil; - case nw_connection_state_ready: { - return [[GNCNWFrameworkSocket alloc] - initWithConnection:[[GNCNWConnectionImpl alloc] initWithNWConnection:connection]]; - } - } + return [_nwFramework connectToHost:host + port:port + includePeerToPeer:YES + cancelSource:cancelSource + queue:_hotspot_queue + error:error]; } - (NSString *)getCurrentWifiSSID { diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h index ffa47398..1ec551f7 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h @@ -156,12 +156,17 @@ typedef void (^ServiceUpdateHandler)(NSString *_Nonnull serviceName, * @param host The IPv4 address to connect to. * @param port The port to connect to. * @param includePeerToPeer Whether to include peer-to-peer services. + * @param cancelSource An optional dispatch source to cancel the connection attempt. + * @param queue An optional dispatch queue to use for connection events. If nil, the main queue is + * used. * @param[out] error Error that will be populated on failure. * @return Returns a connected socket or nil if an error has occured. */ - (nullable GNCNWFrameworkSocket *)connectToHost:(GNCIPv4Address *)host port:(NSInteger)port includePeerToPeer:(BOOL)includePeerToPeer + cancelSource:(nullable dispatch_source_t)cancelSource + queue:(nullable dispatch_queue_t)queue error:(NSError **_Nullable)error; /** diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m index dd5ec672..dfdc9ea2 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.m @@ -69,6 +69,9 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu PSKIdentity:(nullable NSData *)PSKIdentity PSKSharedSecret:(nullable NSData *)PSKSharedSecret includePeerToPeer:(BOOL)includePeerToPeer + cancelSource: + (nullable dispatch_source_t)cancelSource + queue:(nullable dispatch_queue_t)queue error:(NSError **)error; @end @@ -319,6 +322,8 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu PSKIdentity:nil PSKSharedSecret:nil includePeerToPeer:_includePeerToPeer + cancelSource:nil + queue:nil error:error]; } @@ -336,12 +341,16 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu PSKIdentity:PSKIdentity PSKSharedSecret:PSKSharedSecret includePeerToPeer:_includePeerToPeer + cancelSource:nil + queue:nil error:error]; } - (nullable GNCNWFrameworkSocket *)connectToHost:(GNCIPv4Address *)host port:(NSInteger)port includePeerToPeer:(BOOL)includePeerToPeer + cancelSource:(nullable dispatch_source_t)cancelSource + queue:(nullable dispatch_queue_t)queue error:(NSError **)error { GNCLoggerInfo(@"[GNCNWFramework] Connect to host {host:%s, port:%ld}.", host.dottedRepresentation.UTF8String, (long)port); @@ -351,6 +360,8 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu PSKIdentity:nil PSKSharedSecret:nil includePeerToPeer:(BOOL)includePeerToPeer + cancelSource:cancelSource + queue:queue error:error]; } @@ -360,6 +371,9 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu PSKIdentity:(nullable NSData *)PSKIdentity PSKSharedSecret:(nullable NSData *)PSKSharedSecret includePeerToPeer:(BOOL)includePeerToPeer + cancelSource: + (nullable dispatch_source_t)cancelSource + queue:(nullable dispatch_queue_t)queue error:(NSError **)error { NSCondition *condition = [[NSCondition alloc] init]; [condition lock]; @@ -378,7 +392,7 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu return nil; } nw_connection_t connection = nw_connection_create(endpoint, parameters); - nw_connection_set_queue(connection, dispatch_get_main_queue()); + nw_connection_set_queue(connection, queue ? queue : dispatch_get_main_queue()); nw_connection_set_state_changed_handler( connection, ^(nw_connection_state_t state, nw_error_t error) { [condition lock]; @@ -392,6 +406,14 @@ NSDictionary *GNCTXTRecordForBrowseResult(nw_browse_resu } [condition unlock]; }); + if (cancelSource) { + dispatch_source_set_event_handler(cancelSource, ^{ + GNCLoggerInfo( + @"[GNCNWFramework] Connection to endpoint was cancelled before it could be established."); + nw_connection_cancel(connection); + dispatch_source_cancel(cancelSource); + }); + } nw_connection_start(connection); BOOL didSignal = diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h index 72204e2a..30c048a1 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h @@ -38,8 +38,10 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) BOOL connectedToIncludePeerToPeer; @property(nonatomic) NSInteger listenedForServiceOnPort; @property(nonatomic) BOOL listenedForServiceIncludePeerToPeer; -@property(nonatomic, readonly) NSMutableArray* sockets; -@property(nonatomic, readonly) NSMutableArray* serverSockets; +@property(nonatomic, readonly) NSMutableArray* sockets; +@property(nonatomic, readonly) NSMutableArray* serverSockets; +@property(nonatomic, nullable) dispatch_source_t connectedWithCancelSource; +@property(nonatomic, nullable) dispatch_queue_t connectedWithQueue; @end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m index f05fe3ce..44ce46d6 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m @@ -60,8 +60,8 @@ error:(NSError **)error { self.connectedToServiceName = serviceName; self.connectedToServiceType = serviceType; - GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc] - initWithConnection:[[GNCFakeNWConnection alloc] init]]; + GNCFakeNWFrameworkSocket *socket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:[[GNCFakeNWConnection alloc] init]]; [self.sockets addObject:socket]; return socket; } @@ -73,8 +73,8 @@ error:(NSError **)error { self.connectedToServiceName = serviceName; self.connectedToServiceType = serviceType; - GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc] - initWithConnection:[[GNCFakeNWConnection alloc] init]]; + GNCFakeNWFrameworkSocket *socket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:[[GNCFakeNWConnection alloc] init]]; [self.sockets addObject:socket]; return socket; } @@ -82,12 +82,16 @@ - (GNCNWFrameworkSocket *)connectToHost:(GNCIPv4Address *)host port:(NSInteger)port includePeerToPeer:(BOOL)includePeerToPeer + cancelSource:(nullable dispatch_source_t)cancelSource + queue:(nullable dispatch_queue_t)queue error:(NSError **)error { self.connectedToHost = host; self.connectedToPort = port; self.connectedToIncludePeerToPeer = includePeerToPeer; - GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc] - initWithConnection:[[GNCFakeNWConnection alloc] init]]; + self.connectedWithCancelSource = cancelSource; + self.connectedWithQueue = queue; + GNCFakeNWFrameworkSocket *socket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:[[GNCFakeNWConnection alloc] init]]; [self.sockets addObject:socket]; return socket; } diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m index b26560a6..6bdaf67c 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h" @implementation GNCFakeNWFrameworkSocket @@ -35,6 +35,10 @@ if (self.dataToRead) { NSData *data = self.dataToRead; self.dataToRead = nil; + if (data.length > length) { + self.dataToRead = [data subdataWithRange:NSMakeRange(length, data.length - length)]; + return [data subdataWithRange:NSMakeRange(0, length)]; + } return data; } return [NSData data]; diff --git a/internal/platform/implementation/apple/Tests/BUILD b/internal/platform/implementation/apple/Tests/BUILD index 9ee589fb..caedf5b3 100644 --- a/internal/platform/implementation/apple/Tests/BUILD +++ b/internal/platform/implementation/apple/Tests/BUILD @@ -35,6 +35,7 @@ objc_library( "GNCSingleThreadExecutorTest.mm", "GNCTimerTest.mm", "GNCUtilsTest.m", + "GNCWifiHotspotMediumTest.mm", "GNCWifiLanMediumTest.mm", "UtilsTest.mm", "ble_medium_test.mm", @@ -51,6 +52,8 @@ objc_library( "//internal/platform/implementation/apple:ble_v2", "//internal/platform/implementation/apple/Mediums/BLE", "//internal/platform/implementation/apple/Mediums/BLE/Tests:BLETestsLib", + "//internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake", + "//internal/platform/implementation/apple/Mediums/Hotspot", "//internal/platform/implementation/apple/Mediums/WiFiCommon", "//internal/platform/implementation/apple/Mediums/WiFiCommon/Tests:FakeNWFramework", "//third_party/apple_frameworks:CommonCrypto", diff --git a/internal/platform/implementation/apple/Tests/GNCWifiHotspotMediumTest.mm b/internal/platform/implementation/apple/Tests/GNCWifiHotspotMediumTest.mm new file mode 100644 index 00000000..5f326594 --- /dev/null +++ b/internal/platform/implementation/apple/Tests/GNCWifiHotspotMediumTest.mm @@ -0,0 +1,171 @@ +// Copyright 2025 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 "internal/platform/implementation/apple/wifi_hotspot.h" + +#import +#import + +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#import "internal/platform/implementation/apple/Mediums/CoreLocation/CLLocationManager/Fake/CLLocationManagerFake.h" +#import "internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h" + +namespace { +const int kPort = 1234; +const char kIPAddress[] = "192.168.1.2"; +} // namespace + +// Expose for testing +@interface GNCHotspotMedium (Testing) +@property(nonatomic) CLLocationManager *locationManager; +@end + +@interface GNCWifiHotspotMediumTest : XCTestCase +@end + +@implementation GNCWifiHotspotMediumTest { + GNCFakeNWFramework *_fakeNWFramework; + GNCHotspotMedium *_medium; + CLLocationManagerFake *_fakeLocationManager; + std::unique_ptr _hotspotMedium; +} + +- (void)setUp { + [super setUp]; + _fakeNWFramework = [[GNCFakeNWFramework alloc] init]; + _medium = [[GNCHotspotMedium alloc] initWithQueue:dispatch_get_main_queue() + nwFramework:_fakeNWFramework]; + _fakeLocationManager = [[CLLocationManagerFake alloc] init]; + _medium.locationManager = _fakeLocationManager; + _hotspotMedium = std::make_unique(_medium); +} + +- (void)tearDown { + _hotspotMedium.reset(); + _fakeNWFramework = nil; + _medium = nil; + _fakeLocationManager = nil; + [super tearDown]; +} + +- (void)testConnectWifiHotspot { + nearby::HotspotCredentials hotspotCredentials; + hotspotCredentials.SetSSID("TestSSID"); + hotspotCredentials.SetPassword("TestPassword"); + + XCTAssertFalse(_hotspotMedium->ConnectWifiHotspot(&hotspotCredentials)); +} + +- (void)testListenForService { + // iOS does not support acting as a Wifi Hotspot server. + XCTAssertTrue(_hotspotMedium->ListenForService(1234) == nullptr); +} + +- (void)testConnectToService { + nearby::CancellationFlag cancellationFlag; + + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + + XCTAssertTrue(socket != nullptr); + XCTAssertEqualObjects(_fakeNWFramework.connectedToHost.dottedRepresentation, + [NSString stringWithUTF8String:kIPAddress]); + XCTAssertEqual(_fakeNWFramework.connectedToPort, kPort); + XCTAssertTrue(_fakeNWFramework.connectedToIncludePeerToPeer); +} + +- (void)testInputStreamRead { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject; + NSData *data = [@"TestData" dataUsingEncoding:NSUTF8StringEncoding]; + fakeSocket.dataToRead = data; + + nearby::ExceptionOr readData = socket->GetInputStream().Read(4); + + XCTAssertTrue(readData.ok()); + XCTAssertEqual(readData.result().size(), 4); + XCTAssertEqual(strncmp(readData.result().data(), "Test", 4), 0); +} + +- (void)testInputStreamClose { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject; + + nearby::Exception closeResult = socket->GetInputStream().Close(); + + XCTAssertTrue(closeResult.Ok()); + XCTAssertFalse(fakeSocket.isClosed); +} + +- (void)testOutputStreamWrite { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject; + NSData *data = [@"TestData" dataUsingEncoding:NSUTF8StringEncoding]; + nearby::ByteArray byteArray(reinterpret_cast(data.bytes), data.length); + + nearby::Exception writeResult = socket->GetOutputStream().Write(byteArray); + + XCTAssertTrue(writeResult.Ok()); + XCTAssertEqualObjects(fakeSocket.writtenData, data); +} + +- (void)testSocketClose { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject; + + nearby::Exception closeResult = socket->Close(); + + XCTAssertTrue(closeResult.Ok()); + XCTAssertTrue(fakeSocket.isClosed); +} + +- (void)testOutputStreamFlush { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + + nearby::Exception flushResult = socket->GetOutputStream().Flush(); + + XCTAssertTrue(flushResult.Ok()); +} + +- (void)testOutputStreamClose { + nearby::CancellationFlag cancellationFlag; + std::unique_ptr socket = + _hotspotMedium->ConnectToService(kIPAddress, kPort, &cancellationFlag); + GNCFakeNWFrameworkSocket *fakeSocket = _fakeNWFramework.sockets.firstObject; + + nearby::Exception closeResult = socket->GetOutputStream().Close(); + + XCTAssertTrue(closeResult.Ok()); + XCTAssertFalse(fakeSocket.isClosed); +} + +@end diff --git a/internal/platform/implementation/apple/network_utils.mm b/internal/platform/implementation/apple/network_utils.mm index 91a1637c..d0e2811d 100644 --- a/internal/platform/implementation/apple/network_utils.mm +++ b/internal/platform/implementation/apple/network_utils.mm @@ -153,6 +153,8 @@ GNCNWFrameworkSocket* ConnectToService(GNCNWFramework* medium, const std::string GNCNWFrameworkSocket* socket = [medium connectToHost:host port:port includePeerToPeer:include_peer_to_peer + cancelSource:nil + queue:nil error:&error]; if (socket != nil) { return socket; diff --git a/internal/platform/implementation/apple/wifi_hotspot.h b/internal/platform/implementation/apple/wifi_hotspot.h index b624f1e7..9e1c0bd0 100644 --- a/internal/platform/implementation/apple/wifi_hotspot.h +++ b/internal/platform/implementation/apple/wifi_hotspot.h @@ -15,7 +15,7 @@ #ifndef PLATFORM_IMPL_APPLE_WIFI_HOTSPOT_H_ #define PLATFORM_IMPL_APPLE_WIFI_HOTSPOT_H_ -#import // NOLINT +#import // NOLINT #import #include @@ -27,6 +27,7 @@ @class GNCMBonjourService; @class GNCHotspotMedium; @class GNCNWFrameworkSocket; +@class GNCNWFrameworkServerSocket; namespace nearby { namespace apple { @@ -68,7 +69,6 @@ class WifiHotspotOutputStream : public OutputStream { class WifiHotspotSocket : public api::WifiHotspotSocket { public: explicit WifiHotspotSocket(GNCNWFrameworkSocket* socket); - explicit WifiHotspotSocket() = default; ~WifiHotspotSocket() override = default; InputStream& GetInputStream() override; OutputStream& GetOutputStream() override; @@ -86,6 +86,8 @@ class WifiHotspotSocket : public api::WifiHotspotSocket { class WifiHotspotMedium : public api::WifiHotspotMedium { public: WifiHotspotMedium(); + // For testing only. + explicit WifiHotspotMedium(GNCHotspotMedium* hotspot_medium); ~WifiHotspotMedium() override; WifiHotspotMedium(const WifiHotspotMedium&) = delete; @@ -121,10 +123,11 @@ class WifiHotspotMedium : public api::WifiHotspotMedium { absl::string_view ip_address, int port, CancellationFlag* cancellation_flag) override; // IOS is not supporting WifiHotspot Server yet. - bool StartWifiHotspot(HotspotCredentials* hotspot_credentials) override {return false;} - bool StopWifiHotspot() override {return false;} - std::unique_ptr ListenForService( - int port) override { return nil; } + bool StartWifiHotspot(HotspotCredentials* hotspot_credentials) override { return false; } + bool StopWifiHotspot() override { return false; } + std::unique_ptr ListenForService(int port) override { + return nullptr; + } /** * @brief Gets the dynamic port range for the Wifi Hotspot. @@ -132,8 +135,7 @@ class WifiHotspotMedium : public api::WifiHotspotMedium { * @return An optional pair of integers representing the minimum and maximum dynamic port numbers, * or absl::nullopt if not supported. */ - std::optional> GetDynamicPortRange() - override { + std::optional> GetDynamicPortRange() override { return std::nullopt; } diff --git a/internal/platform/implementation/apple/wifi_hotspot.mm b/internal/platform/implementation/apple/wifi_hotspot.mm index c5453803..86ec5e43 100644 --- a/internal/platform/implementation/apple/wifi_hotspot.mm +++ b/internal/platform/implementation/apple/wifi_hotspot.mm @@ -26,7 +26,10 @@ #import "internal/platform/implementation/apple/Log/GNCLogger.h" #import "internal/platform/implementation/apple/Mediums/Hotspot/GNCHotspotMedium.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.h" #import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h" +#import "internal/platform/implementation/apple/network_utils.h" namespace nearby { namespace apple { @@ -103,6 +106,8 @@ WifiHotspotMedium::WifiHotspotMedium() { medium_ = [[GNCHotspotMedium alloc] initWithQueue:hotspot_queue_]; } +WifiHotspotMedium::WifiHotspotMedium(GNCHotspotMedium* hotspot_medium) : medium_(hotspot_medium) {} + WifiHotspotMedium::~WifiHotspotMedium() { DisconnectWifiHotspot(); } bool WifiHotspotMedium::ConnectWifiHotspot(HotspotCredentials* hotspot_credentials_) {