diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h index 10386fcf..babca0b8 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h @@ -35,10 +35,15 @@ byte3:(uint8_t)byte3 byte4:(uint8_t)byte4 NS_DESIGNATED_INITIALIZER; +/** Creates an IPv4 address from a 4 byte integer. */ + (nonnull instancetype)addressFromFourByteInt:(uint32_t)address; +/** Creates an IPv4 address from a 4 byte data object. */ + (nonnull instancetype)addressFromData:(nonnull NSData *)address; +/** Creates an IPv4 address from a human readable dotted representation. */ ++ (nullable instancetype)addressWithDottedRepresentation:(nonnull NSString *)address; + /** The first byte of the IP address. */ @property(nonatomic, readonly) uint8_t byte1; diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.m index 9ae7961f..fdd40135 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.m +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.m @@ -47,6 +47,33 @@ byte4:bytes[3]]; } ++ (nullable instancetype)addressWithDottedRepresentation:(NSString *)address { + NSArray *components = [address componentsSeparatedByString:@"."]; + if (components.count != 4) { + return nil; + } + + uint8_t bytes[4]; + for (int i = 0; i < 4; ++i) { + NSScanner *scanner = [NSScanner scannerWithString:components[i]]; + int value; + // The string must be a valid integer and must not contain any other characters. + if (![scanner scanInt:&value] || ![scanner isAtEnd]) { + return nil; + } + + if (value < 0 || value > 255) { + return nil; + } + bytes[i] = (uint8_t)value; + } + + return [[GNCIPv4Address alloc] initWithByte1:bytes[0] + byte2:bytes[1] + byte3:bytes[2] + byte4:bytes[3]]; +} + - (NSData *)binaryRepresentation { const uint8_t bytes[] = {_byte1, _byte2, _byte3, _byte4}; return [NSData dataWithBytes:bytes length:4]; diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/BUILD b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/BUILD index 2c812091..4f698547 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/BUILD +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/BUILD @@ -20,6 +20,25 @@ licenses(["notice"]) package(default_visibility = ["//:__subpackages__"]) +objc_library( + name = "FakeNWFramework", + testonly = True, + srcs = [ + "GNCFakeNWFramework.m", + "GNCFakeNWFrameworkServerSocket.m", + "GNCFakeNWFrameworkSocket.m", + ], + hdrs = [ + "GNCFakeNWFramework.h", + "GNCFakeNWFrameworkServerSocket.h", + "GNCFakeNWFrameworkSocket.h", + ], + deps = [ + "//internal/platform/implementation/apple/Mediums/WiFiCommon", + "//third_party/apple_frameworks:Foundation", + ], +) + objc_library( name = "WiFiCommonTestsLib", testonly = True, diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h new file mode 100644 index 00000000..72204e2a --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h @@ -0,0 +1,46 @@ +// 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. + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFramework.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class GNCFakeNWFrameworkSocket; +@class GNCFakeNWFrameworkServerSocket; + +/** A fake implementation of @c GNCNWFramework to inject for testing. */ +@interface GNCFakeNWFramework : GNCNWFramework + +@property(nonatomic, nullable) NSNumber* startedAdvertisingPort; +@property(nonatomic, nullable) NSString* startedAdvertisingServiceName; +@property(nonatomic, nullable) NSString* startedAdvertisingServiceType; +@property(nonatomic, nullable) NSNumber* stoppedAdvertisingPort; +@property(nonatomic, nullable) NSString* startedDiscoveryServiceType; +@property(nonatomic) BOOL startedDiscoveryIncludePeerToPeer; +@property(nonatomic, nullable) NSString* stoppedDiscoveryServiceType; +@property(nonatomic, nullable) NSString* connectedToServiceName; +@property(nonatomic, nullable) NSString* connectedToServiceType; +@property(nonatomic, nullable) GNCIPv4Address* connectedToHost; +@property(nonatomic) NSInteger connectedToPort; +@property(nonatomic) BOOL connectedToIncludePeerToPeer; +@property(nonatomic) NSInteger listenedForServiceOnPort; +@property(nonatomic) BOOL listenedForServiceIncludePeerToPeer; +@property(nonatomic, readonly) NSMutableArray* sockets; +@property(nonatomic, readonly) NSMutableArray* serverSockets; + +@end + +NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m new file mode 100644 index 00000000..3e8629eb --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.m @@ -0,0 +1,94 @@ +// 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. + +#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" + +@implementation GNCFakeNWFramework + +- (instancetype)init { + self = [super init]; + if (self) { + _sockets = [NSMutableArray array]; + _serverSockets = [NSMutableArray array]; + } + return self; +} + +- (void)startAdvertisingPort:(NSInteger)port + serviceName:(NSString*)serviceName + serviceType:(NSString*)serviceType + txtRecords:(NSDictionary*)txtRecords { + self.startedAdvertisingPort = @(port); + self.startedAdvertisingServiceName = serviceName; + self.startedAdvertisingServiceType = serviceType; +} + +- (void)stopAdvertisingPort:(NSInteger)port { + self.stoppedAdvertisingPort = @(port); +} + +- (BOOL)startDiscoveryForServiceType:(NSString*)serviceType + serviceFoundHandler:(nonnull ServiceUpdateHandler)serviceFoundHandler + serviceLostHandler:(nonnull ServiceUpdateHandler)serviceLostHandler + includePeerToPeer:(BOOL)includePeerToPeer + error:(NSError**)error { + self.startedDiscoveryServiceType = serviceType; + self.startedDiscoveryIncludePeerToPeer = includePeerToPeer; + return YES; +} + +- (void)stopDiscoveryForServiceType:(NSString*)serviceType { + self.stoppedDiscoveryServiceType = serviceType; +} + +- (GNCNWFrameworkSocket*)connectToServiceName:(NSString*)serviceName + serviceType:(NSString*)serviceType + error:(NSError**)error { + self.connectedToServiceName = serviceName; + self.connectedToServiceType = serviceType; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* socket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + [self.sockets addObject:socket]; + return socket; +} + +- (GNCNWFrameworkSocket*)connectToHost:(GNCIPv4Address*)host + port:(NSInteger)port + includePeerToPeer:(BOOL)includePeerToPeer + error:(NSError**)error { + self.connectedToHost = host; + self.connectedToPort = port; + self.connectedToIncludePeerToPeer = includePeerToPeer; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* socket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + [self.sockets addObject:socket]; + return socket; +} + +- (GNCNWFrameworkServerSocket*)listenForServiceOnPort:(NSInteger)port + includePeerToPeer:(BOOL)includePeerToPeer + error:(NSError**)error { + self.listenedForServiceOnPort = port; + self.listenedForServiceIncludePeerToPeer = includePeerToPeer; + GNCFakeNWFrameworkServerSocket* serverSocket = + [[GNCFakeNWFrameworkServerSocket alloc] initWithPort:port]; + [self.serverSockets addObject:serverSocket]; + return serverSocket; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h new file mode 100644 index 00000000..465cee69 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h @@ -0,0 +1,32 @@ +// 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. + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +@class GNCFakeNWFrameworkSocket; + +/** A fake implementation of @c GNCNWFrameworkServerSocket to inject for testing. */ +@interface GNCFakeNWFrameworkServerSocket : GNCNWFrameworkServerSocket + +@property(nonatomic) BOOL isClosed; +@property(nonatomic, nullable) NSError* acceptError; +@property(nonatomic, nullable) GNCFakeNWFrameworkSocket* socketToReturnOnAccept; + +@end + +NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.m new file mode 100644 index 00000000..eef5bc01 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.m @@ -0,0 +1,54 @@ +// 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. + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h" + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h" +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h" + +@implementation GNCFakeNWFrameworkServerSocket { + GNCIPv4Address *_ipAddress; +} + +- (instancetype)initWithPort:(NSInteger)port { + self = [super initWithPort:port]; + if (self) { + _ipAddress = [[GNCIPv4Address alloc] initWithByte1:192 byte2:168 byte3:1 byte4:1]; + } + return self; +} + +- (GNCIPv4Address *)ipAddress { + return _ipAddress; +} + +- (nullable GNCNWFrameworkSocket *)acceptWithError:(NSError **)error { + if (self.acceptError) { + if (error) { + *error = self.acceptError; + } + return nil; + } + if (self.socketToReturnOnAccept) { + return self.socketToReturnOnAccept; + } + nw_connection_t connection = (nw_connection_t) @"mock connection"; + return [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; +} + +- (void)close { + self.isClosed = YES; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h new file mode 100644 index 00000000..4d0f9571 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h @@ -0,0 +1,32 @@ +// 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. + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h" + +#import + +NS_ASSUME_NONNULL_BEGIN + +/** A fake implementation of @c GNCNWFrameworkSocket to inject for testing. */ +@interface GNCFakeNWFrameworkSocket : GNCNWFrameworkSocket + +@property(nonatomic, nullable) NSData* dataToRead; +@property(nonatomic, readonly) NSMutableData* writtenData; +@property(nonatomic) BOOL isClosed; +@property(nonatomic, nullable) NSError* readError; +@property(nonatomic, nullable) NSError* writeError; + +@end + +NS_ASSUME_NONNULL_END diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m new file mode 100644 index 00000000..71227bc9 --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.m @@ -0,0 +1,57 @@ +// 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. + +#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h" + +@implementation GNCFakeNWFrameworkSocket + +- (instancetype)initWithConnection:(nw_connection_t)connection { + self = [super initWithConnection:connection]; + if (self) { + _writtenData = [NSMutableData data]; + } + return self; +} + +- (nullable NSData *)readMaxLength:(NSUInteger)length error:(NSError **)error { + if (self.readError) { + if (error) { + *error = self.readError; + } + return nil; + } + if (self.dataToRead) { + NSData *data = self.dataToRead; + self.dataToRead = nil; + return data; + } + return [NSData data]; +} + +- (BOOL)write:(NSData *)data error:(NSError **)error { + if (self.writeError) { + if (error) { + *error = self.writeError; + } + return NO; + } + [self.writtenData appendData:data]; + return YES; +} + +- (void)close { + self.isClosed = YES; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCIPAddressTest.mm b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCIPAddressTest.mm index 75551ca9..f607e8e0 100644 --- a/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCIPAddressTest.mm +++ b/internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCIPAddressTest.mm @@ -111,4 +111,32 @@ XCTAssertEqual(bytes[3], 171); } +- (void)testAddressWithDottedRepresentationWithValidAddress { + GNCIPv4Address *address = + [GNCIPv4Address addressWithDottedRepresentation:@"192.168.1.1"]; + XCTAssertNotNil(address); + XCTAssertEqual(address.byte1, 192); + XCTAssertEqual(address.byte2, 168); + XCTAssertEqual(address.byte3, 1); + XCTAssertEqual(address.byte4, 1); +} + +- (void)testAddressWithDottedRepresentationWithOutOfRangeAddress { + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"256.1.1.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"-1.1.1.1"]); +} + +- (void)testAddressWithDottedRepresentationWithNonNumericAddress { + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"foo.1.1.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"1.foo.1.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"1.1.foo.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"1.1.1.foo"]); +} + +- (void)testAddressWithDottedRepresentationWithInvalidFormatAddress { + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"1.1.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@"1.1.1.1.1"]); + XCTAssertNil([GNCIPv4Address addressWithDottedRepresentation:@""]); +} + @end diff --git a/internal/platform/implementation/apple/Tests/BUILD b/internal/platform/implementation/apple/Tests/BUILD index b03f55dc..878c246a 100644 --- a/internal/platform/implementation/apple/Tests/BUILD +++ b/internal/platform/implementation/apple/Tests/BUILD @@ -31,6 +31,7 @@ objc_library( "GNCScheduledExecutorTest.mm", "GNCSingleThreadExecutorTest.mm", "GNCUtilsTest.m", + "GNCWifiLanMediumTest.mm", "UtilsTest.mm", ], deps = [ @@ -40,6 +41,8 @@ objc_library( "//internal/platform/implementation/apple", # buildcleaner: keep "//internal/platform/implementation/apple:Shared", "//internal/platform/implementation/apple:ble_v2", + "//internal/platform/implementation/apple/Mediums/WiFiCommon", + "//internal/platform/implementation/apple/Mediums/WiFiCommon/Tests:FakeNWFramework", "//third_party/apple_frameworks:CommonCrypto", "//third_party/apple_frameworks:Foundation", "//third_party/apple_frameworks:XCTest", diff --git a/internal/platform/implementation/apple/Tests/GNCWifiLanMediumTest.mm b/internal/platform/implementation/apple/Tests/GNCWifiLanMediumTest.mm new file mode 100644 index 00000000..d717b09e --- /dev/null +++ b/internal/platform/implementation/apple/Tests/GNCWifiLanMediumTest.mm @@ -0,0 +1,257 @@ +// 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. + +#import + +#include "internal/platform/implementation/apple/wifi_lan.h" + +#include + +#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" + +@interface GNCWifiLanMediumTest : XCTestCase +@end + +@implementation GNCWifiLanMediumTest { + GNCFakeNWFramework* _fakeNWFramework; + std::unique_ptr _wifiLanMedium; +} + +- (void)setUp { + [super setUp]; + _fakeNWFramework = [[GNCFakeNWFramework alloc] init]; + _wifiLanMedium = std::make_unique(_fakeNWFramework); +} + +- (void)tearDown { + _wifiLanMedium.reset(); + _fakeNWFramework = nil; + [super tearDown]; +} + +- (void)testStartAdvertising { + nearby::NsdServiceInfo nsdServiceInfo; + nsdServiceInfo.SetServiceType("_my-service._tcp"); + nsdServiceInfo.SetServiceName("My Service"); + nsdServiceInfo.SetPort(1234); + + XCTAssertTrue(_wifiLanMedium->StartAdvertising(nsdServiceInfo)); + XCTAssertEqualObjects(_fakeNWFramework.startedAdvertisingPort, @(1234)); + XCTAssertEqualObjects(_fakeNWFramework.startedAdvertisingServiceName, @"My Service"); + XCTAssertEqualObjects(_fakeNWFramework.startedAdvertisingServiceType, @"_my-service._tcp"); +} + +- (void)testStopAdvertising { + nearby::NsdServiceInfo nsdServiceInfo; + nsdServiceInfo.SetPort(1234); + + XCTAssertTrue(_wifiLanMedium->StopAdvertising(nsdServiceInfo)); + XCTAssertEqualObjects(_fakeNWFramework.stoppedAdvertisingPort, @(1234)); +} + +- (void)testStartDiscovery { + std::string serviceType = "_my-service._tcp"; + + XCTAssertTrue(_wifiLanMedium->StartDiscovery( + serviceType, nearby::api::WifiLanMedium::DiscoveredServiceCallback{})); + XCTAssertEqualObjects(_fakeNWFramework.startedDiscoveryServiceType, @"_my-service._tcp"); + XCTAssertFalse(_fakeNWFramework.startedDiscoveryIncludePeerToPeer); +} + +- (void)testStopDiscovery { + std::string serviceType = "_my-service._tcp"; + + XCTAssertTrue(_wifiLanMedium->StopDiscovery(serviceType)); + XCTAssertEqualObjects(_fakeNWFramework.stoppedDiscoveryServiceType, @"_my-service._tcp"); +} + +- (void)testConnectToServiceWithNsdServiceInfo { + nearby::NsdServiceInfo nsdServiceInfo; + nsdServiceInfo.SetServiceType("_my-service._tcp"); + nsdServiceInfo.SetServiceName("My Service"); + + nearby::CancellationFlag cancellationFlag; + _wifiLanMedium->ConnectToService(nsdServiceInfo, &cancellationFlag); + XCTAssertEqualObjects(_fakeNWFramework.connectedToServiceName, @"My Service"); + XCTAssertEqualObjects(_fakeNWFramework.connectedToServiceType, @"_my-service._tcp"); +} + +- (void)testConnectToServiceWithIpAddressAndPort { + std::string ipAddress = "\xC0\xA8\x01\x01"; // 192.168.1.1 + int port = 1234; + + nearby::CancellationFlag cancellationFlag; + _wifiLanMedium->ConnectToService(ipAddress, port, &cancellationFlag); + + XCTAssertEqualObjects([_fakeNWFramework.connectedToHost dottedRepresentation], @"192.168.1.1"); + XCTAssertEqual(_fakeNWFramework.connectedToPort, 1234); + XCTAssertFalse(_fakeNWFramework.connectedToIncludePeerToPeer); +} + +- (void)testListenForService { + int port = 1234; + + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(port); + + XCTAssertTrue(serverSocket != nullptr); + XCTAssertEqual(_fakeNWFramework.listenedForServiceOnPort, 1234); + XCTAssertFalse(_fakeNWFramework.listenedForServiceIncludePeerToPeer); +} + +- (void)testSocketAndStream { + // Create a server socket. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + XCTAssertTrue(serverSocket != nullptr); + + GNCFakeNWFrameworkServerSocket* fakeServerSocket = + (GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0]; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* fakeSocket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + fakeServerSocket.socketToReturnOnAccept = fakeSocket; + + // Accept a client socket. + std::unique_ptr clientSocket = serverSocket->Accept(); + XCTAssertTrue(clientSocket != nullptr); + + // Test input stream. + nearby::InputStream& inputStream = clientSocket->GetInputStream(); + fakeSocket.dataToRead = [@"test data" dataUsingEncoding:NSUTF8StringEncoding]; + nearby::ExceptionOr readData = inputStream.Read(9); + XCTAssertTrue(readData.ok()); + XCTAssertEqual(std::string(readData.result()), "test data"); + + // Test output stream. + nearby::OutputStream& outputStream = clientSocket->GetOutputStream(); + nearby::ByteArray writeData("write data"); + XCTAssertTrue(outputStream.Write(writeData).Ok()); + XCTAssertEqualObjects(fakeSocket.writtenData, + [@"write data" dataUsingEncoding:NSUTF8StringEncoding]); + + // Test closing the socket. + XCTAssertTrue(clientSocket->Close().Ok()); + XCTAssertTrue(fakeSocket.isClosed); + + // Test closing the server socket. + XCTAssertTrue(serverSocket->Close().Ok()); + XCTAssertTrue(fakeServerSocket.isClosed); +} + +- (void)testServerSocketGetIPAddress { + // Create a server socket. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + XCTAssertTrue(serverSocket != nullptr); + + // IP address is hardcoded in GNCFakeNWFrameworkServerSocket to 192.168.1.1 + GNCIPv4Address* ipAddress = [GNCIPv4Address addressWithDottedRepresentation:@"192.168.1.1"]; + NSData* ipAddressData = ipAddress.binaryRepresentation; + XCTAssertEqual(serverSocket->GetIPAddress(), + std::string((char*)ipAddressData.bytes, ipAddressData.length)); + + // Test closing the server socket. + XCTAssertTrue(serverSocket->Close().Ok()); +} + +- (void)testServerSocketGetPort { + // Create a server socket. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + XCTAssertTrue(serverSocket != nullptr); + + XCTAssertEqual(serverSocket->GetPort(), 1234); + + // Test closing the server socket. + XCTAssertTrue(serverSocket->Close().Ok()); +} + +- (void)testOutputStreamClose { + // Create a server socket and accept a client. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + GNCFakeNWFrameworkServerSocket* fakeServerSocket = + (GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0]; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* fakeSocket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + fakeServerSocket.socketToReturnOnAccept = fakeSocket; + std::unique_ptr clientSocket = serverSocket->Accept(); + XCTAssertTrue(clientSocket != nullptr); + + // Get the output stream. + nearby::OutputStream& outputStream = clientSocket->GetOutputStream(); + + // Close the output stream. + XCTAssertTrue(outputStream.Close().Ok()); + XCTAssertFalse(fakeSocket.isClosed); + + // Close the server socket. + XCTAssertTrue(serverSocket->Close().Ok()); +} + +- (void)testOutputStreamFlush { + // Create a server socket and accept a client. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + GNCFakeNWFrameworkServerSocket* fakeServerSocket = + (GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0]; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* fakeSocket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + fakeServerSocket.socketToReturnOnAccept = fakeSocket; + std::unique_ptr clientSocket = serverSocket->Accept(); + XCTAssertTrue(clientSocket != nullptr); + + // Get the output stream. + nearby::OutputStream& outputStream = clientSocket->GetOutputStream(); + + // Flush the output stream. + XCTAssertTrue(outputStream.Flush().Ok()); + + // Close the socket and server socket. + XCTAssertTrue(clientSocket->Close().Ok()); + XCTAssertTrue(serverSocket->Close().Ok()); +} + +- (void)testInputStreamClose { + // Create a server socket and accept a client. + std::unique_ptr serverSocket = + _wifiLanMedium->ListenForService(1234); + GNCFakeNWFrameworkServerSocket* fakeServerSocket = + (GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0]; + nw_connection_t connection = (nw_connection_t) @"mock connection"; + GNCFakeNWFrameworkSocket* fakeSocket = + [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection]; + fakeServerSocket.socketToReturnOnAccept = fakeSocket; + std::unique_ptr clientSocket = serverSocket->Accept(); + XCTAssertTrue(clientSocket != nullptr); + + // Get the input stream. + nearby::InputStream& inputStream = clientSocket->GetInputStream(); + + // Close the input stream. + XCTAssertTrue(inputStream.Close().Ok()); + XCTAssertFalse(fakeSocket.isClosed); + + // Close the server socket. + XCTAssertTrue(serverSocket->Close().Ok()); +} + +@end diff --git a/internal/platform/implementation/apple/wifi_lan.h b/internal/platform/implementation/apple/wifi_lan.h index b37db3e5..f7939a48 100644 --- a/internal/platform/implementation/apple/wifi_lan.h +++ b/internal/platform/implementation/apple/wifi_lan.h @@ -103,6 +103,8 @@ class WifiLanServerSocket : public api::WifiLanServerSocket { class WifiLanMedium : public api::WifiLanMedium { public: WifiLanMedium(); + // For testing only. + explicit WifiLanMedium(GNCNWFramework* medium); ~WifiLanMedium() override = default; WifiLanMedium(const WifiLanMedium&) = delete; diff --git a/internal/platform/implementation/apple/wifi_lan.mm b/internal/platform/implementation/apple/wifi_lan.mm index fb9c7c8e..3cfe23af 100644 --- a/internal/platform/implementation/apple/wifi_lan.mm +++ b/internal/platform/implementation/apple/wifi_lan.mm @@ -124,6 +124,8 @@ Exception WifiLanServerSocket::Close() { WifiLanMedium::WifiLanMedium() { medium_ = [[GNCNWFramework alloc] init]; } +WifiLanMedium::WifiLanMedium(GNCNWFramework* medium) : medium_(medium) {} + bool WifiLanMedium::StartAdvertising(const NsdServiceInfo& nsd_service_info) { return network_utils::StartAdvertising(medium_, nsd_service_info); }