diff --git a/internal/platform/implementation/apple/Mediums/BUILD b/internal/platform/implementation/apple/Mediums/BUILD index 5a48bef8..7f7cdd3e 100644 --- a/internal/platform/implementation/apple/Mediums/BUILD +++ b/internal/platform/implementation/apple/Mediums/BUILD @@ -29,6 +29,7 @@ objc_library( "WifiLan/GNCMBonjourService.m", "WifiLan/GNCMBonjourUtils.m", "WifiLanV2/GNCWiFiLANError.m", + "WifiLanV2/GNCWiFiLANMedium.m", "WifiLanV2/GNCWiFiLANServerSocket.m", "WifiLanV2/GNCWiFiLANServerSocket+Internal.h", "WifiLanV2/GNCWiFiLANSocket.m", @@ -45,6 +46,7 @@ objc_library( "WifiLan/GNCMBonjourService.h", "WifiLan/GNCMBonjourUtils.h", "WifiLanV2/GNCWiFiLANError.h", + "WifiLanV2/GNCWiFiLANMedium.h", "WifiLanV2/GNCWiFiLANServerSocket.h", "WifiLanV2/GNCWiFiLANSocket.h", ], diff --git a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.h b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.h new file mode 100644 index 00000000..5cbea6ef --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.h @@ -0,0 +1,64 @@ +// Copyright 2023 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 + +@class GNCWiFiLANServerSocket; + +/** + * The @c GNCWiFiLANMedium object is used as a delegate by the platform abstraction layer for + * Wi-Fi LAN related functionality on Apple platforms. + */ +@interface GNCWiFiLANMedium : NSObject + +/** + * Listens for incoming connections on a given port. + * + * This function will always be called before a call to + * startAdvertisingServiceName:serviceType:error:, but may also be called on its own without a call + * to start advertising the service. This function will also be called during upgrade attempts on + * the advertising side. + * + * This function should block execution until ready for incoming connections. + * + * @param port The port on which the listener can accept connections. Should be a number between 1 + * and 65536 to open a server socket on that exact port. Zero can be used to listen on + * a random port. + * @param[out] error Error that will be populated on failure. + * @return Returns a server socket or nil if an error has occured. + */ +- (nullable GNCWiFiLANServerSocket *)listenForServiceOnPort:(NSInteger)port + error:(NSError **_Nullable)error; + +/** + * Creates a Bonjour service that advertises the listener on the local network. + * + * @param port The port of the listener to advertise. + * @param serviceName The Bonjour name of the service. + * @param serviceType The Bonjour type of the service. + * @param txtRecords The TXT record to advertise with the service. + */ +- (void)startAdvertisingPort:(NSInteger)port + serviceName:(nonnull NSString *)serviceName + serviceType:(nonnull NSString *)serviceType + txtRecords:(nonnull NSDictionary *)txtRecords; + +/** + * Removes the Bonjour service advertisement. + * + * @param port The port of the listener to stop advertising. + */ +- (void)stopAdvertisingPort:(NSInteger)port; + +@end diff --git a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.m b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.m new file mode 100644 index 00000000..c84143ee --- /dev/null +++ b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANMedium.m @@ -0,0 +1,61 @@ +// Copyright 2023 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/WifiLanV2/GNCWiFiLANMedium.h" + +#import + +#import "internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket+Internal.h" +#import "internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.h" +#import "GoogleToolboxForMac/GTMLogger.h" + +@implementation GNCWiFiLANMedium { + // Usage of .count should be avoided. Zombie keys won't show up in -keyEnumerator, but WILL be + // included in .count until the next time that the internal hashtable is resized. + NSMapTable *_serverSockets; +} + +- (instancetype)init { + if (self = [super init]) { + _serverSockets = [NSMapTable strongToWeakObjectsMapTable]; + } + return self; +} + +- (GNCWiFiLANServerSocket *)listenForServiceOnPort:(NSInteger)port error:(NSError **)error { + GNCWiFiLANServerSocket *serverSocket = [[GNCWiFiLANServerSocket alloc] initWithPort:port]; + BOOL success = [serverSocket startListeningWithError:error]; + if (success) { + [_serverSockets setObject:serverSocket forKey:@(serverSocket.port)]; + return serverSocket; + } + return nil; +} + +- (void)startAdvertisingPort:(NSInteger)port + serviceName:(NSString *)serviceName + serviceType:(NSString *)serviceType + txtRecords:(NSDictionary *)txtRecords { + GNCWiFiLANServerSocket *serverSocket = [_serverSockets objectForKey:@(port)]; + [serverSocket startAdvertisingServiceName:serviceName + serviceType:serviceType + txtRecords:txtRecords]; +} + +- (void)stopAdvertisingPort:(NSInteger)port { + GNCWiFiLANServerSocket *serverSocket = [_serverSockets objectForKey:@(port)]; + [serverSocket stopAdvertising]; +} + +@end diff --git a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.h b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.h index 70a3f075..9acbc1c7 100644 --- a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.h +++ b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.h @@ -28,7 +28,7 @@ * * @param port The port of the server socket. */ -- (nonnull instancetype)initWithPort:(nonnull NSNumber *)port NS_DESIGNATED_INITIALIZER; +- (nonnull instancetype)initWithPort:(NSInteger)port NS_DESIGNATED_INITIALIZER; /** * The 4 byte binary representation for the IPv4 address of the physical network interface. @@ -38,7 +38,7 @@ /** * The port of the server socket. */ -@property(nonatomic, readonly, copy) NSNumber *port; +@property(nonatomic, readonly) NSInteger port; /** * Returns an arbitrary, connected socket, ready to exchange data. diff --git a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.m b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.m index cdad6380..2fa2c955 100644 --- a/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.m +++ b/internal/platform/implementation/apple/Mediums/WifiLanV2/GNCWiFiLANServerSocket.m @@ -41,10 +41,10 @@ NSError *_listenerError; } -- (instancetype)initWithPort:(NSNumber *)port { +- (instancetype)initWithPort:(NSInteger)port { self = [super init]; if (self) { - _port = [port copy]; + _port = port; _condition = [[NSCondition alloc] init]; _listenerState = nw_listener_state_invalid; } @@ -117,10 +117,10 @@ // If the server socket port is zero, a random port will be selected. The server socket port will // be updated to reflect the port it's listening on, once the listener transitions to the ready // state. - if ([self.port intValue] == 0) { + if (self.port == 0) { _listener = nw_listener_create(parameters); } else { - _listener = nw_listener_create_with_port([[self.port stringValue] UTF8String], parameters); + _listener = nw_listener_create_with_port([[@(self.port) stringValue] UTF8String], parameters); } // Register to listen for incoming connections. @@ -202,7 +202,7 @@ [self close]; return NO; case nw_listener_state_ready: - _port = @(nw_listener_get_port(_listener)); + _port = nw_listener_get_port(_listener); return YES; } }