[NC Apple coverage] Added unit tests for GNCNWFrameworkSocket in WiFiCommon

PiperOrigin-RevId: 803485731
This commit is contained in:
Edwin Wu
2025-09-05 09:10:16 -07:00
committed by Copybara-Service
parent beaab63d4b
commit dca5a4b6ec
22 changed files with 586 additions and 193 deletions
@@ -24,6 +24,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/GNCNWFrameworkError.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h"
@@ -234,7 +235,8 @@ static const UInt8 kConnectionToHostTimeoutInSeconds = 10;
GNCLoggerError(@"connectToEndpoint failed with result: %d", blockResult);
return nil;
case nw_connection_state_ready: {
return [[GNCNWFrameworkSocket alloc] initWithConnection:connection];
return [[GNCNWFrameworkSocket alloc]
initWithConnection:[[GNCNWConnectionImpl alloc] initWithNWConnection:connection]];
}
}
}
@@ -26,6 +26,7 @@ objc_library(
name = "WiFiCommon",
srcs = [
"GNCIPv4Address.m",
"GNCNWConnectionImpl.m",
"GNCNWFramework.m",
"GNCNWFrameworkError.m",
"GNCNWFrameworkServerSocket.m",
@@ -34,6 +35,8 @@ objc_library(
],
hdrs = [
"GNCIPv4Address.h",
"GNCNWConnection.h",
"GNCNWConnectionImpl.h",
"GNCNWFramework.h",
"GNCNWFrameworkError.h",
"GNCNWFrameworkServerSocket.h",
@@ -0,0 +1,74 @@
// 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 <Foundation/Foundation.h>
#import <Network/Network.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A protocol to wrap nw_connection_t C functions to allow for faking in tests.
*/
@protocol GNCNWConnection <NSObject>
@optional
/**
* Initializes the connection object with a Network.framework connection object.
*
* @param connection The underlying nw_connection_t object.
*/
- (instancetype)initWithNWConnection:(nw_connection_t)connection;
@required
/**
* Receives a message from the connection.
*
* @param minIncompleteLength The minimum number of bytes to receive before the completion handler is called.
* @param maxLengths The maximum number of bytes to receive.
* @param handler The completion handler to call when the receive is complete or an error occurs.
*/
- (void)receiveMessageWithMinLength:(uint32_t)minIncompleteLength
maxLength:(uint32_t)maxLengths
completionHandler:(void (^)(dispatch_data_t _Nullable content,
nw_content_context_t _Nullable context,
bool isComplete, nw_error_t _Nullable error))handler;
/**
* Sends data over the connection.
*
* @param content The data to send.
* @param context The content context to use for the send.
* @param isComplete A boolean indicating if this is the complete message.
* @param handler The completion handler to call when the send is complete or an error occurs.
*/
- (void)sendData:(dispatch_data_t)content
context:(nw_content_context_t)context
isComplete:(BOOL)isComplete
completionHandler:(void (^)(nw_error_t _Nullable error))handler;
/**
* Cancels the connection.
*/
- (void)cancel;
/**
* Returns the underlying nw_connection_t object.
*
* @return The underlying nw_connection_t object, or nil if not applicable.
*/
- (nullable nw_connection_t)nwConnection;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,33 @@
// 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/GNCNWConnection.h"
#import <Foundation/Foundation.h>
#import <Network/Network.h>
NS_ASSUME_NONNULL_BEGIN
/**
* The default implementation of GNCNWConnection that calls the real Network.framework C
* functions.
*/
@interface GNCNWConnectionImpl : NSObject <GNCNWConnection>
- (instancetype)initWithNWConnection:(nw_connection_t)connection NS_DESIGNATED_INITIALIZER;
- (instancetype)init NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,59 @@
// 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/GNCNWConnectionImpl.h"
#import <Foundation/Foundation.h>
#import <Network/Network.h>
NS_ASSUME_NONNULL_BEGIN
@implementation GNCNWConnectionImpl {
nw_connection_t _connection;
}
- (instancetype)initWithNWConnection:(nw_connection_t)connection {
self = [super init];
if (self) {
_connection = connection;
}
return self;
}
- (void)receiveMessageWithMinLength:(uint32_t)minIncompleteLength
maxLength:(uint32_t)maxLength
completionHandler:(void (^)(dispatch_data_t _Nullable content,
nw_content_context_t _Nullable context,
bool isComplete, nw_error_t _Nullable error))handler {
nw_connection_receive(_connection, minIncompleteLength, maxLength, handler);
}
- (void)sendData:(dispatch_data_t)content
context:(nw_content_context_t)context
isComplete:(BOOL)isComplete
completionHandler:(void (^)(nw_error_t _Nullable error))handler {
nw_connection_send(_connection, content, context, isComplete, handler);
}
- (void)cancel {
nw_connection_cancel(_connection);
}
- (nullable nw_connection_t)nwConnection {
return _connection;
}
@end
NS_ASSUME_NONNULL_END
@@ -19,6 +19,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/GNCNWFrameworkError.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket.h"
@@ -421,7 +422,8 @@ NSDictionary<NSString *, NSString *> *GNCTXTRecordForBrowseResult(nw_browse_resu
case nw_connection_state_cancelled:
return nil;
case nw_connection_state_ready:
return [[GNCNWFrameworkSocket alloc] initWithConnection:connection];
return [[GNCNWFrameworkSocket alloc]
initWithConnection:[[GNCNWConnectionImpl alloc] initWithNWConnection:connection]];
}
}
@@ -24,6 +24,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/GNCNWFrameworkError.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h"
@@ -109,7 +110,8 @@ NS_ASSUME_NONNULL_BEGIN
if (connection == nil) {
return nil;
}
return [[GNCNWFrameworkSocket alloc] initWithConnection:connection];
return [[GNCNWFrameworkSocket alloc]
initWithConnection:[[GNCNWConnectionImpl alloc] initWithNWConnection:connection]];
}
- (void)close {
@@ -15,6 +15,8 @@
#import <Foundation/Foundation.h>
#import <Network/Network.h>
@protocol GNCNWConnection;
@interface GNCNWFrameworkSocket : NSObject
/**
@@ -23,13 +25,11 @@
- (nonnull instancetype)init NS_UNAVAILABLE;
/**
* Creates a socket that allows reading/writing for a given connection.
* Creates a socket that allows reading/writing for a given connection wrapper.
*
* @param connection A bidirectional data connection between a local and remote endpoint. This class
* will take ownership of connection and manage its lifetime. The connection
* should not be shared or reused.
* @param connection A wrapper around the underlying network connection.
*/
- (nonnull instancetype)initWithConnection:(nonnull nw_connection_t)connection
- (nonnull instancetype)initWithConnection:(nonnull id<GNCNWConnection>)connection
NS_DESIGNATED_INITIALIZER;
/**
@@ -18,17 +18,21 @@
#import <Network/Network.h>
#import "internal/platform/implementation/apple/Log/GNCLogger.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWConnection.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkError.h"
static const NSTimeInterval kConnectionWriteTimeout = 5.0; // 5 seconds timeout
@interface GNCNWFrameworkSocket ()
@property(nonatomic, readonly) nw_connection_t connection;
@property(nonatomic, readonly) id<GNCNWConnection> connection;
@end
@implementation GNCNWFrameworkSocket {
}
- (instancetype)initWithConnection:(nw_connection_t)connection {
- (instancetype)initWithConnection:(nonnull id<GNCNWConnection>)connection {
self = [super init];
if (self) {
_connection = connection;
@@ -37,8 +41,12 @@
}
- (NSData *)readMaxLength:(NSUInteger)length error:(NSError **)error {
__strong nw_connection_t connection = self.connection;
if (connection == nil) {
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorUnknown
userInfo:nil];
}
return nil;
}
@@ -48,22 +56,25 @@
__block NSData *blockResult = nil;
__block NSError *blockError = nil;
nw_connection_receive(
connection, length, length,
^(dispatch_data_t content, nw_content_context_t context, bool is_complete, nw_error_t error) {
[condition lock];
if (error != nil) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(error);
}
[self.connection
receiveMessageWithMinLength:(uint32_t)length
maxLength:(uint32_t)length
completionHandler:^(dispatch_data_t _Nullable content,
nw_content_context_t _Nullable context, bool isComplete,
nw_error_t _Nullable receiveError) {
[condition lock];
if (receiveError) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(receiveError);
}
#if __LP64__
// This cast is only safe in a 64-bit runtime.
blockResult = [(NSData *)content copy];
// This cast is only safe in a 64-bit runtime.
blockResult = [(NSData *)content copy];
#else
blockResult = nil;
#endif
[condition signal];
[condition unlock];
});
[condition signal];
[condition unlock];
}];
[condition wait];
[condition unlock];
@@ -75,15 +86,19 @@
}
- (BOOL)write:(NSData *)data error:(NSError **)error {
__strong nw_connection_t connection = self.connection;
if (connection == nil) {
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorUnknown
userInfo:nil];
}
return NO;
}
NSCondition *condition = [[NSCondition alloc] init];
[condition lock];
__block BOOL blockResult = NO;
__block BOOL blockSuccess = NO;
__block NSError *blockError = nil;
__block NSData *blockData = [data copy];
@@ -97,32 +112,32 @@
blockData = nil;
});
nw_connection_send(connection, dispatchData, NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT, false,
^(nw_error_t error) {
[condition lock];
blockResult = error == nil;
if (error != nil) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(error);
}
[condition signal];
[condition unlock];
});
[self.connection sendData:dispatchData
context:NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT
isComplete:false
completionHandler:^(nw_error_t _Nullable sendError) {
[condition lock];
blockSuccess = sendError == nil;
if (sendError) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(sendError);
}
[condition signal];
[condition unlock];
}];
[condition wait];
// Wait until the condition is signaled or 5 seconds pass
BOOL signaled =
[condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:kConnectionWriteTimeout]];
[condition unlock];
if (error != nil) {
*error = blockError;
}
return blockResult;
return signaled && blockSuccess;
}
- (void)close {
__strong nw_connection_t connection = self.connection;
if (connection == nil) {
return;
}
nw_connection_cancel(connection);
[self.connection cancel];
_connection = nil;
}
@@ -24,11 +24,13 @@ objc_library(
name = "FakeNWFramework",
testonly = True,
srcs = [
"GNCFakeNWConnection.m",
"GNCFakeNWFramework.m",
"GNCFakeNWFrameworkServerSocket.m",
"GNCFakeNWFrameworkSocket.m",
],
hdrs = [
"GNCFakeNWConnection.h",
"GNCFakeNWFramework.h",
"GNCFakeNWFrameworkServerSocket.h",
"GNCFakeNWFrameworkSocket.h",
@@ -36,6 +38,7 @@ objc_library(
deps = [
"//internal/platform/implementation/apple/Mediums/WiFiCommon",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:Network",
],
)
@@ -44,13 +47,17 @@ objc_library(
testonly = True,
srcs = [
"GNCIPAddressTest.mm",
"GNCNWConnectionImplTest.m",
"GNCNWFrameworkServerSocketTest.m",
"GNCNWFrameworkSocketTest.m",
"GNCNWFrameworkTest.m",
"GNCNWParametersTest.m",
],
deps = [
":FakeNWFramework",
"//internal/platform/implementation/apple/Mediums/WiFiCommon",
"//third_party/apple_frameworks:Foundation",
"//third_party/apple_frameworks:Network",
"//third_party/apple_frameworks:XCTest",
"//third_party/objective_c/ocmock/v3:OCMock",
],
@@ -0,0 +1,45 @@
// 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/GNCNWConnection.h"
#import <Foundation/Foundation.h>
#import <Network/Network.h>
NS_ASSUME_NONNULL_BEGIN
/**
* A fake implementation of GNCNWConnection for testing purposes.
*/
@interface GNCFakeNWConnection : NSObject <GNCNWConnection>
/** The data to be received by the fake connection. */
@property(nonatomic, nullable) dispatch_data_t dataToReceive;
/** Whether cancel has been called on the fake connection. */
@property(nonatomic) BOOL cancelCalled;
/** Property to simulate send failure. */
@property(nonatomic) BOOL simulateSendFailure;
/** Property to simulate receive failure. */
@property(nonatomic) BOOL simulateReceiveFailure;
- (instancetype)init NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithNWConnection:(nw_connection_t)connection NS_UNAVAILABLE;
@end
NS_ASSUME_NONNULL_END
@@ -0,0 +1,66 @@
// 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/GNCFakeNWConnection.h"
#import <Foundation/Foundation.h>
#import <Network/Network.h>
NS_ASSUME_NONNULL_BEGIN
@implementation GNCFakeNWConnection
- (instancetype)init {
return [super init];
}
- (void)receiveMessageWithMinLength:(uint32_t)minIncompleteLength
maxLength:(uint32_t)maxLength
completionHandler:(void (^)(dispatch_data_t _Nullable content,
nw_content_context_t _Nullable context,
bool isComplete, nw_error_t _Nullable error))handler {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
if (self.simulateReceiveFailure) {
// We cannot create a realistic nw_error_t, so use nil content to signal failure.
handler(nil, nil, YES, nil);
} else {
handler(self.dataToReceive, nil, YES, nil);
}
});
}
- (void)sendData:(dispatch_data_t)content
context:(nw_content_context_t)context
isComplete:(BOOL)isComplete
completionHandler:(void (^)(nw_error_t _Nullable error))handler {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{
if (self.simulateSendFailure) {
// Simulate an error by timing out, as creating a realistic nw_error_t is not possible.
} else {
handler(nil);
}
});
}
- (void)cancel {
self.cancelCalled = YES;
}
- (nullable nw_connection_t)nwConnection {
return nil; // Fake doesn't wrap a real connection
}
@end
NS_ASSUME_NONNULL_END
@@ -13,6 +13,7 @@
// limitations under the License.
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFramework.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h"
@@ -59,9 +60,8 @@
error:(NSError **)error {
self.connectedToServiceName = serviceName;
self.connectedToServiceType = serviceType;
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWFrameworkSocket *socket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc]
initWithConnection:[[GNCFakeNWConnection alloc] init]];
[self.sockets addObject:socket];
return socket;
}
@@ -73,9 +73,8 @@
error:(NSError **)error {
self.connectedToServiceName = serviceName;
self.connectedToServiceType = serviceType;
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWFrameworkSocket *socket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc]
initWithConnection:[[GNCFakeNWConnection alloc] init]];
[self.sockets addObject:socket];
return socket;
}
@@ -87,9 +86,8 @@
self.connectedToHost = host;
self.connectedToPort = port;
self.connectedToIncludePeerToPeer = includePeerToPeer;
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWFrameworkSocket *socket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
GNCFakeNWFrameworkSocket *socket = [[GNCFakeNWFrameworkSocket alloc]
initWithConnection:[[GNCFakeNWConnection alloc] init]];
[self.sockets addObject:socket];
return socket;
}
@@ -24,8 +24,14 @@ NS_ASSUME_NONNULL_BEGIN
@interface GNCFakeNWFrameworkServerSocket : GNCNWFrameworkServerSocket
@property(nonatomic) BOOL isClosed;
@property(nonatomic, nullable) NSError* acceptError;
@property(nonatomic, nullable) GNCFakeNWFrameworkSocket* socketToReturnOnAccept;
@property(nonatomic, nullable) NSError *acceptError;
@property(nonatomic, nullable) GNCFakeNWFrameworkSocket *socketToReturnOnAccept;
@property(nonatomic) BOOL startAdvertisingCalled;
@property(nonatomic, nullable, copy) NSString *startAdvertisingServiceName;
@property(nonatomic, nullable, copy) NSString *startAdvertisingServiceType;
@property(nonatomic, nullable, copy)
NSDictionary<NSString *, NSString *> *startAdvertisingTXTRecords;
@property(nonatomic) BOOL stopAdvertisingCalled;
@end
@@ -15,6 +15,7 @@
#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/GNCFakeNWConnection.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkSocket.h"
@implementation GNCFakeNWFrameworkServerSocket {
@@ -43,12 +44,27 @@
if (self.socketToReturnOnAccept) {
return self.socketToReturnOnAccept;
}
nw_connection_t connection = (nw_connection_t) @"mock connection";
return [[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
return [[GNCFakeNWFrameworkSocket alloc]
initWithConnection:[[GNCFakeNWConnection alloc] init]];
}
- (void)close {
self.isClosed = YES;
}
#pragma mark - GNCNWFrameworkServerSocket+Internal.h
- (void)startAdvertisingServiceName:(NSString *)serviceName
serviceType:(NSString *)serviceType
txtRecords:(NSDictionary<NSString *, NSString *> *)txtRecords {
self.startAdvertisingCalled = YES;
self.startAdvertisingServiceName = serviceName;
self.startAdvertisingServiceType = serviceType;
self.startAdvertisingTXTRecords = txtRecords;
}
- (void)stopAdvertising {
self.stopAdvertisingCalled = YES;
}
@end
@@ -16,11 +16,15 @@
#import <Foundation/Foundation.h>
@protocol GNCNWConnection;
NS_ASSUME_NONNULL_BEGIN
/** A fake implementation of @c GNCNWFrameworkSocket to inject for testing. */
@interface GNCFakeNWFrameworkSocket : GNCNWFrameworkSocket
- (instancetype)initWithConnection:(id<GNCNWConnection>)connection NS_DESIGNATED_INITIALIZER;
@property(nonatomic, nullable) NSData* dataToRead;
@property(nonatomic, readonly) NSMutableData* writtenData;
@property(nonatomic) BOOL isClosed;
@@ -12,11 +12,12 @@
// 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"
@implementation GNCFakeNWFrameworkSocket
- (instancetype)initWithConnection:(nw_connection_t)connection {
- (instancetype)initWithConnection:(id<GNCNWConnection>)connection {
self = [super initWithConnection:connection];
if (self) {
_writtenData = [NSMutableData data];
@@ -0,0 +1,59 @@
// 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/GNCNWConnectionImpl.h"
#import <XCTest/XCTest.h>
#import <Network/Network.h>
#import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h"
NS_ASSUME_NONNULL_BEGIN
@interface GNCNWConnectionImplTests : XCTestCase
@end
@implementation GNCNWConnectionImplTests {
id _mockNWConnection;
GNCNWConnectionImpl *_connectionImpl;
}
- (void)setUp {
[super setUp];
_mockNWConnection = OCMProtocolMock(@protocol(OS_nw_connection));
_connectionImpl = [[GNCNWConnectionImpl alloc] initWithNWConnection:_mockNWConnection];
}
- (void)tearDown {
OCMStopMocking(_mockNWConnection);
[super tearDown];
}
- (void)testInit {
XCTAssertNotNil(_connectionImpl);
}
// NOTE: The methods in GNCNWConnectionImpl call C functions from the Network.framework
// (e.g., nw_connection_cancel, nw_connection_send, nw_connection_receive).
// OCMock cannot directly mock these C functions. To test these interactions,
// a lower-level interception method or a different testing strategy would be required.
// We can only test the basic initialization and property access here.
- (void)testNWConnection {
XCTAssertEqual([_connectionImpl nwConnection], _mockNWConnection);
}
@end
NS_ASSUME_NONNULL_END
@@ -12,30 +12,101 @@
// See the License for the specific language governing permissions and
// limitations under the License.
#import <XCTest/XCTest.h>
#import <os/availability.h>
#import <Network/Network.h>
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h"
#import "third_party/objective_c/ocmock/v3/Source/OCMock/OCMock.h"
@interface GNCNWFrameworkSocketTest : XCTestCase
#import <Network/Network.h>
#import <XCTest/XCTest.h>
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWConnection.h"
NS_ASSUME_NONNULL_BEGIN
@interface GNCNWFrameworkSocketTests : XCTestCase
@end
@implementation GNCNWFrameworkSocketTest {
id _mockConnection;
@implementation GNCNWFrameworkSocketTests {
GNCFakeNWConnection *_fakeConnection;
GNCNWFrameworkSocket *_socket;
}
- (void)setUp {
[super setUp];
if (@available(iOS 13.0, *)) {
_mockConnection = OCMProtocolMock(@protocol(OS_nw_connection));
}
_fakeConnection = [[GNCFakeNWConnection alloc] init];
_socket = [[GNCNWFrameworkSocket alloc] initWithConnection:_fakeConnection];
}
- (void)testInitWithConnection API_AVAILABLE(ios(13.0)) {
GNCNWFrameworkSocket *socket = [[GNCNWFrameworkSocket alloc] initWithConnection:_mockConnection];
XCTAssertNotNil(socket);
- (void)tearDown {
_socket = nil;
_fakeConnection = nil;
[super tearDown];
}
- (void)testInit {
XCTAssertNotNil(_socket);
}
- (void)testReadMaxLength_Success {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
_fakeConnection.dataToReceive = (dispatch_data_t)testData;
NSData *receivedData = [_socket readMaxLength:testData.length error:&error];
XCTAssertEqualObjects(receivedData, testData);
XCTAssertNil(error);
}
- (void)testReadMaxLength_Error {
NSError *error = nil;
_fakeConnection.simulateReceiveFailure = YES;
NSData *receivedData = [_socket readMaxLength:10 error:&error];
XCTAssertNil(receivedData);
XCTAssertNil(error); // Fake doesn't produce an NSError
}
- (void)testReadMaxLength_Zero {
NSError *error = nil;
NSData *receivedData = [_socket readMaxLength:0 error:&error];
XCTAssertNil(receivedData);
XCTAssertNil(error);
}
- (void)testWrite_Success {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [_socket write:testData error:&error];
XCTAssertTrue(result);
XCTAssertNil(error);
}
- (void)testWrite_Error {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
_fakeConnection.simulateSendFailure = YES;
BOOL result = [_socket write:testData error:&error];
XCTAssertFalse(result);
}
- (void)testClose {
XCTAssertFalse(_fakeConnection.cancelCalled);
[_socket close];
XCTAssertTrue(_fakeConnection.cancelCalled);
// Also test that subsequent operations fail
NSError *error = nil;
XCTAssertNil([_socket readMaxLength:10 error:&error]);
XCTAssertFalse([_socket write:[NSData data] error:&error]);
}
@end
NS_ASSUME_NONNULL_END
@@ -20,80 +20,8 @@
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCIPv4Address.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkError.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkServerSocket+Internal.h"
// A mock GNCNWFrameworkServerSocket used to test the advertising/listening methods on
// GNCNWFramework.
@interface GNCMockNWFrameworkServerSocket : GNCNWFrameworkServerSocket
@property(nonatomic) BOOL startListeningResult;
@property(nonatomic, nullable) NSError *startListeningError;
@property(nonatomic, nullable, copy) NSData *startListeningPSKIdentity;
@property(nonatomic, nullable, copy) NSData *startListeningPSKSharedSecret;
@property(nonatomic) BOOL startListeningIncludePeerToPeer;
@property(nonatomic) BOOL startAdvertisingCalled;
@property(nonatomic, nullable, copy) NSString *startAdvertisingServiceName;
@property(nonatomic, nullable, copy) NSString *startAdvertisingServiceType;
@property(nonatomic, nullable, copy) NSDictionary<NSString *, NSString *> *startAdvertisingTXTRecords;
@property(nonatomic) BOOL stopAdvertisingCalled;
- (instancetype)initWithPort:(NSInteger)port NS_DESIGNATED_INITIALIZER;
@end
@implementation GNCMockNWFrameworkServerSocket
@synthesize startListeningResult;
@synthesize startListeningError;
@synthesize startListeningPSKIdentity;
@synthesize startListeningPSKSharedSecret;
@synthesize startListeningIncludePeerToPeer;
@synthesize startAdvertisingCalled;
@synthesize startAdvertisingServiceName;
@synthesize startAdvertisingServiceType;
@synthesize startAdvertisingTXTRecords;
@synthesize stopAdvertisingCalled;
- (instancetype)initWithPort:(NSInteger)port {
self = [super initWithPort:port];
if (self) {
self.startListeningResult = YES;
}
return self;
}
- (BOOL)startListeningWithError:(NSError **)error
includePeerToPeer:(BOOL)includePeerToPeer {
self.startListeningIncludePeerToPeer = includePeerToPeer;
if (!self.startListeningResult && error != nil) {
*error = self.startListeningError;
}
return self.startListeningResult;
}
- (BOOL)startListeningWithPSKIdentity:(NSData *)PSKIdentity
PSKSharedSecret:(NSData *)PSKSharedSecret
includePeerToPeer:(BOOL)includePeerToPeer
error:(NSError **)error {
self.startListeningPSKIdentity = PSKIdentity;
self.startListeningPSKSharedSecret = PSKSharedSecret;
self.startListeningIncludePeerToPeer = includePeerToPeer;
if (!self.startListeningResult && error != nil) {
*error = self.startListeningError;
}
return self.startListeningResult;
}
- (void)startAdvertisingServiceName:(NSString *)serviceName
serviceType:(NSString *)serviceType
txtRecords:(NSDictionary<NSString *, NSString *> *)txtRecords {
self.startAdvertisingCalled = YES;
self.startAdvertisingServiceName = serviceName;
self.startAdvertisingServiceType = serviceType;
self.startAdvertisingTXTRecords = txtRecords;
}
- (void)stopAdvertising {
self.stopAdvertisingCalled = YES;
}
@end
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/GNCNWFrameworkSocket.h"
#import "internal/platform/implementation/apple/Mediums/WiFiCommon/Tests/GNCFakeNWFrameworkServerSocket.h"
@interface GNCNWFrameworkTest : XCTestCase
@end
@@ -116,13 +44,13 @@
id mockServerSocketAlloc = OCMClassMock([GNCNWFrameworkServerSocket class]);
OCMStub([mockServerSocketAlloc alloc]).andReturn(mockServerSocketAlloc);
OCMStub([mockServerSocketAlloc initWithPort:1234]).andReturn(mockServerSocket);
OCMStub([mockServerSocket startListeningWithError:[OCMArg anyObjectRef]
includePeerToPeer:NO])
OCMStub([mockServerSocket startListeningWithError:[OCMArg anyObjectRef] includePeerToPeer:NO])
.andReturn(YES);
NSError *error = nil;
GNCNWFrameworkServerSocket *serverSocket =
[framework listenForServiceOnPort:1234 includePeerToPeer:NO error:&error];
GNCNWFrameworkServerSocket *serverSocket = [framework listenForServiceOnPort:1234
includePeerToPeer:NO
error:&error];
XCTAssertNotNil(serverSocket);
XCTAssertNil(error);
XCTAssertTrue([framework isListeningForAnyService]);
@@ -134,8 +62,7 @@
id mockServerSocketAlloc = OCMClassMock([GNCNWFrameworkServerSocket class]);
OCMStub([mockServerSocketAlloc alloc]).andReturn(mockServerSocketAlloc);
OCMStub([mockServerSocketAlloc initWithPort:1234]).andReturn(mockServerSocket);
OCMStub([mockServerSocket startListeningWithError:[OCMArg anyObjectRef]
includePeerToPeer:NO])
OCMStub([mockServerSocket startListeningWithError:[OCMArg anyObjectRef] includePeerToPeer:NO])
.andDo(^(id localSelf, NSError **error, BOOL includePeerToPeer) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EACCES userInfo:nil];
@@ -144,8 +71,9 @@
});
NSError *error = nil;
GNCNWFrameworkServerSocket *serverSocket =
[framework listenForServiceOnPort:1234 includePeerToPeer:NO error:&error];
GNCNWFrameworkServerSocket *serverSocket = [framework listenForServiceOnPort:1234
includePeerToPeer:NO
error:&error];
XCTAssertNil(serverSocket);
XCTAssertNotNil(error);
XCTAssertFalse([framework isListeningForAnyService]);
@@ -161,18 +89,18 @@
OCMStub([mockServerSocketAlloc alloc]).andReturn(mockServerSocketAlloc);
OCMStub([mockServerSocketAlloc initWithPort:1234]).andReturn(mockServerSocket);
OCMStub([mockServerSocket startListeningWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
includePeerToPeer:YES
error:[OCMArg anyObjectRef]])
PSKSharedSecret:pskSharedSecret
includePeerToPeer:YES
error:[OCMArg anyObjectRef]])
.andReturn(YES);
NSError *error = nil;
GNCNWFrameworkServerSocket *serverSocket = [framework
listenForServiceWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
port:1234
includePeerToPeer:YES
error:&error];
GNCNWFrameworkServerSocket *serverSocket =
[framework listenForServiceWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
port:1234
includePeerToPeer:YES
error:&error];
XCTAssertNotNil(serverSocket);
XCTAssertNil(error);
XCTAssertTrue([framework isListeningForAnyService]);
@@ -188,9 +116,9 @@
OCMStub([mockServerSocketAlloc alloc]).andReturn(mockServerSocketAlloc);
OCMStub([mockServerSocketAlloc initWithPort:1234]).andReturn(mockServerSocket);
OCMStub([mockServerSocket startListeningWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
includePeerToPeer:NO
error:[OCMArg anyObjectRef]])
PSKSharedSecret:pskSharedSecret
includePeerToPeer:NO
error:[OCMArg anyObjectRef]])
.andDo(^(id localSelf, NSData *PSKIdentity, NSData *PSKSharedSecret, BOOL includePeerToPeer,
NSError **error) {
if (error) {
@@ -200,12 +128,12 @@
});
NSError *error = nil;
GNCNWFrameworkServerSocket *serverSocket = [framework
listenForServiceWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
port:1234
includePeerToPeer:NO
error:&error];
GNCNWFrameworkServerSocket *serverSocket =
[framework listenForServiceWithPSKIdentity:pskIdentity
PSKSharedSecret:pskSharedSecret
port:1234
includePeerToPeer:NO
error:&error];
XCTAssertNil(serverSocket);
XCTAssertNotNil(error);
XCTAssertFalse([framework isListeningForAnyService]);
@@ -215,43 +143,43 @@
NSInteger port = 1234;
NSString *serviceName = @"TestService";
NSString *serviceType = @"_test._tcp.";
NSDictionary<NSString *, NSString *> *txtRecords = @{@"key": @"value"};
NSDictionary<NSString *, NSString *> *txtRecords = @{@"key" : @"value"};
GNCMockNWFrameworkServerSocket *mockServerSocket =
[[GNCMockNWFrameworkServerSocket alloc] initWithPort:port];
GNCFakeNWFrameworkServerSocket *fakeServerSocket =
[[GNCFakeNWFrameworkServerSocket alloc] initWithPort:port];
GNCNWFramework *framework = [[GNCNWFramework alloc] init];
NSMapTable<NSNumber *, GNCNWFrameworkServerSocket *> *serverSockets =
[framework valueForKey:@"_serverSockets"];
[serverSockets setObject:mockServerSocket forKey:@(port)];
[serverSockets setObject:fakeServerSocket forKey:@(port)];
[framework startAdvertisingPort:port
serviceName:serviceName
serviceType:serviceType
txtRecords:txtRecords];
XCTAssertTrue(mockServerSocket.startAdvertisingCalled);
XCTAssertEqualObjects(mockServerSocket.startAdvertisingServiceName, serviceName);
XCTAssertEqualObjects(mockServerSocket.startAdvertisingServiceType, serviceType);
XCTAssertEqualObjects(mockServerSocket.startAdvertisingTXTRecords, txtRecords);
XCTAssertTrue(fakeServerSocket.startAdvertisingCalled);
XCTAssertEqualObjects(fakeServerSocket.startAdvertisingServiceName, serviceName);
XCTAssertEqualObjects(fakeServerSocket.startAdvertisingServiceType, serviceType);
XCTAssertEqualObjects(fakeServerSocket.startAdvertisingTXTRecords, txtRecords);
}
- (void)testStopAdvertisingPort {
NSInteger port = 1234;
GNCMockNWFrameworkServerSocket *mockServerSocket =
[[GNCMockNWFrameworkServerSocket alloc] initWithPort:port];
GNCFakeNWFrameworkServerSocket *fakeServerSocket =
[[GNCFakeNWFrameworkServerSocket alloc] initWithPort:port];
GNCNWFramework *framework = [[GNCNWFramework alloc] init];
NSMapTable<NSNumber *, GNCNWFrameworkServerSocket *> *serverSockets =
[framework valueForKey:@"_serverSockets"];
[serverSockets setObject:mockServerSocket forKey:@(port)];
[serverSockets setObject:fakeServerSocket forKey:@(port)];
XCTAssertTrue([[[serverSockets keyEnumerator] allObjects] containsObject:@(port)]);
[framework stopAdvertisingPort:port];
XCTAssertTrue(mockServerSocket.stopAdvertisingCalled);
XCTAssertFalse([[[serverSockets keyEnumerator] allObjects] containsObject:@(port)]);
XCTAssertTrue(fakeServerSocket.stopAdvertisingCalled);
XCTAssertNil([serverSockets objectForKey:@(port)]);
}
- (void)testIsDiscoveringAnyServiceWhenNoServicesAreDiscovering {
@@ -20,6 +20,7 @@
#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/GNCFakeNWConnection.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"
@@ -134,7 +135,7 @@ static const int kTestPort = 1234;
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -200,7 +201,7 @@ static const int kTestPort = 1234;
_awdlMedium->ListenForService(kTestPort);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -224,7 +225,7 @@ static const int kTestPort = 1234;
_awdlMedium->ListenForService(kTestPort);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -248,7 +249,7 @@ static const int kTestPort = 1234;
_awdlMedium->ListenForService(kTestPort);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -20,6 +20,7 @@
#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/GNCFakeNWConnection.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"
@@ -122,7 +123,7 @@
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -188,7 +189,7 @@
_wifiLanMedium->ListenForService(1234);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -212,7 +213,7 @@
_wifiLanMedium->ListenForService(1234);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;
@@ -236,7 +237,7 @@
_wifiLanMedium->ListenForService(1234);
GNCFakeNWFrameworkServerSocket* fakeServerSocket =
(GNCFakeNWFrameworkServerSocket*)_fakeNWFramework.serverSockets[0];
nw_connection_t connection = (nw_connection_t) @"mock connection";
GNCFakeNWConnection* connection = [[GNCFakeNWConnection alloc] init];
GNCFakeNWFrameworkSocket* fakeSocket =
[[GNCFakeNWFrameworkSocket alloc] initWithConnection:connection];
fakeServerSocket.socketToReturnOnAccept = fakeSocket;