@@ -38,12 +38,14 @@ @interface GCDAsyncSocket ()
3838
3939#if NW_FRAMEWORK_AVAILABLE
4040@property (nonatomic , assign ) nw_connection_t connection;
41+ @property (nonatomic , assign , nullable ) nw_listener_t listener;
4142#endif
4243
4344@property (nonatomic , strong ) dispatch_queue_t socketQueue;
4445@property (nonatomic , strong ) NWStreamBuffer *buffer;
4546@property (nonatomic , strong ) NSMutableArray <NWReadRequest *> *readQueue;
4647@property (nonatomic , assign ) BOOL isReadingContinuously;
48+ @property (atomic , assign ) BOOL isListening;
4749
4850// SSE / streaming text mode
4951@property (nonatomic , strong , nullable ) NWSSEParser *sseParser;
@@ -222,7 +224,11 @@ - (uint16_t)connectedPort {
222224- (uint16_t )localPort {
223225 __block uint16_t port = 0 ;
224226 [self performSyncOnSocketQueue: ^{
225- port = _isConnected ? _localPort : 0 ;
227+ if (_isListening) {
228+ port = _localPort;
229+ } else {
230+ port = _isConnected ? _localPort : 0 ;
231+ }
226232 }];
227233 return port;
228234}
@@ -237,6 +243,9 @@ - (NSString *)localHost {
237243
238244- (void )dealloc {
239245#if NW_FRAMEWORK_AVAILABLE
246+ if (_listener) {
247+ nw_listener_cancel (_listener);
248+ }
240249 if (_connection) {
241250 nw_connection_cancel (_connection);
242251 }
@@ -270,13 +279,230 @@ - (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)e
270279}
271280
272281- (BOOL )acceptOnPort : (uint16_t )port error : (NSError **)errPtr {
273- (void )port;
282+ return [self acceptOnInterface: nil port: port error: errPtr];
283+ }
284+
285+ - (BOOL )acceptOnInterface : (NSString *)interface port : (uint16_t )port error : (NSError **)errPtr {
286+ #if NW_FRAMEWORK_AVAILABLE
287+ if (self.isListening ) {
288+ if (errPtr) {
289+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
290+ code: GCDAsyncSocketErrorAlreadyConnected
291+ userInfo: @{NSLocalizedDescriptionKey : @" Socket is already listening." }];
292+ }
293+ return NO ;
294+ }
295+
296+ nw_parameters_t parameters = nw_parameters_create_secure_tcp (
297+ NW_PARAMETERS_DISABLE_PROTOCOL,
298+ NW_PARAMETERS_DEFAULT_CONFIGURATION
299+ );
300+
301+ if (interface.length > 0 ) {
302+ // Bind to a specific interface/address
303+ NSString *portStr = [NSString stringWithFormat: @" %u " , port];
304+ nw_endpoint_t localEndpoint = nw_endpoint_create_host (interface.UTF8String , portStr.UTF8String );
305+ nw_parameters_set_local_endpoint (parameters, localEndpoint);
306+ }
307+
308+ nw_listener_t listener = nw_listener_create_with_port ([NSString stringWithFormat: @" %u " , port].UTF8String , parameters);
309+ if (!listener) {
310+ if (errPtr) {
311+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
312+ code: GCDAsyncSocketErrorConnectionFailed
313+ userInfo: @{NSLocalizedDescriptionKey : @" Failed to create listener." }];
314+ }
315+ return NO ;
316+ }
317+
318+ self.listener = listener;
319+
320+ __weak typeof (self) weakSelf = self;
321+
322+ nw_listener_set_state_changed_handler (listener, ^(nw_listener_state_t state, nw_error_t _Nullable error) {
323+ __strong typeof (weakSelf) strongSelf = weakSelf;
324+ if (!strongSelf) return ;
325+
326+ switch (state) {
327+ case nw_listener_state_ready: {
328+ strongSelf.isListening = YES ;
329+ uint16_t assignedPort = nw_listener_get_port (listener);
330+ strongSelf.localPort = assignedPort;
331+ break ;
332+ }
333+ case nw_listener_state_failed: {
334+ strongSelf.isListening = NO ;
335+ NSError *nsError = [strongSelf socketErrorWithCode: GCDAsyncSocketErrorConnectionFailed
336+ description: @" Listener failed."
337+ reason: @" NW listener entered failed state"
338+ nwError: error];
339+ [strongSelf disconnectInternalWithError: nsError];
340+ break ;
341+ }
342+ case nw_listener_state_cancelled: {
343+ strongSelf.isListening = NO ;
344+ break ;
345+ }
346+ default :
347+ break ;
348+ }
349+ });
350+
351+ nw_listener_set_new_connection_handler (listener, ^(nw_connection_t newConnection) {
352+ __strong typeof (weakSelf) strongSelf = weakSelf;
353+ if (!strongSelf) return ;
354+
355+ // Create a new GCDAsyncSocket for the accepted connection.
356+ // The new socket inherits the listener's delegate – this matches
357+ // GCDAsyncSocket from CocoaAsyncSocket. The user may reassign the
358+ // delegate on newSocket inside socket:didAcceptNewSocket: if needed.
359+ GCDAsyncSocket *newSocket = [[GCDAsyncSocket alloc ] initWithDelegate: strongSelf.delegate
360+ delegateQueue: strongSelf.delegateQueue
361+ socketQueue: nil ];
362+ newSocket.connection = newConnection;
363+
364+ // State change handler for the accepted connection
365+ nw_connection_set_state_changed_handler (newConnection, ^(nw_connection_state_t state, nw_error_t _Nullable error) {
366+ [newSocket handleStateChange: state error: error];
367+ });
368+
369+ nw_connection_set_queue (newConnection, newSocket.socketQueue );
370+ nw_connection_start (newConnection);
371+
372+ dispatch_async (strongSelf.delegateQueue , ^{
373+ id delegate = strongSelf.delegate ;
374+ if ([delegate respondsToSelector: @selector (socket:didAcceptNewSocket: )]) {
375+ [delegate socket: strongSelf didAcceptNewSocket: newSocket];
376+ }
377+ });
378+ });
379+
380+ nw_listener_set_queue (listener, self.socketQueue );
381+ nw_listener_start (listener);
382+
383+ return YES ;
384+ #else
385+ if (errPtr) {
386+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
387+ code: GCDAsyncSocketErrorConnectionFailed
388+ userInfo: @{NSLocalizedDescriptionKey : @" Network.framework is not available on this platform." }];
389+ }
390+ return NO ;
391+ #endif
392+ }
393+
394+ - (BOOL )acceptOnUrl : (NSURL *)url error : (NSError **)errPtr {
395+ #if NW_FRAMEWORK_AVAILABLE
396+ if (self.isListening ) {
397+ if (errPtr) {
398+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
399+ code: GCDAsyncSocketErrorAlreadyConnected
400+ userInfo: @{NSLocalizedDescriptionKey : @" Socket is already listening." }];
401+ }
402+ return NO ;
403+ }
404+
405+ if (!url.isFileURL ) {
406+ if (errPtr) {
407+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
408+ code: GCDAsyncSocketErrorInvalidParameter
409+ userInfo: @{NSLocalizedDescriptionKey : @" URL must be a file URL for Unix Domain Socket." }];
410+ }
411+ return NO ;
412+ }
413+
414+ nw_parameters_t parameters = nw_parameters_create_secure_tcp (
415+ NW_PARAMETERS_DISABLE_PROTOCOL,
416+ NW_PARAMETERS_DEFAULT_CONFIGURATION
417+ );
418+
419+ // Remove existing socket file if present
420+ NSString *path = url.path ;
421+ [[NSFileManager defaultManager ] removeItemAtPath: path error: nil ];
422+
423+ // Construct a unix:// URL for the endpoint (Network.framework expects this scheme)
424+ NSString *unixURLString = [NSString stringWithFormat: @" unix://%@ " , path];
425+ nw_endpoint_t localEndpoint = nw_endpoint_create_url (unixURLString.UTF8String );
426+ nw_parameters_set_local_endpoint (parameters, localEndpoint);
427+
428+ nw_listener_t listener = nw_listener_create (parameters);
429+ if (!listener) {
430+ if (errPtr) {
431+ *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
432+ code: GCDAsyncSocketErrorConnectionFailed
433+ userInfo: @{NSLocalizedDescriptionKey : @" Failed to create Unix Domain Socket listener." }];
434+ }
435+ return NO ;
436+ }
437+
438+ self.listener = listener;
439+
440+ __weak typeof (self) weakSelf = self;
441+
442+ nw_listener_set_state_changed_handler (listener, ^(nw_listener_state_t state, nw_error_t _Nullable error) {
443+ __strong typeof (weakSelf) strongSelf = weakSelf;
444+ if (!strongSelf) return ;
445+
446+ switch (state) {
447+ case nw_listener_state_ready: {
448+ strongSelf.isListening = YES ;
449+ break ;
450+ }
451+ case nw_listener_state_failed: {
452+ strongSelf.isListening = NO ;
453+ NSError *nsError = [strongSelf socketErrorWithCode: GCDAsyncSocketErrorConnectionFailed
454+ description: @" Unix Domain Socket listener failed."
455+ reason: @" NW listener entered failed state"
456+ nwError: error];
457+ [strongSelf disconnectInternalWithError: nsError];
458+ break ;
459+ }
460+ case nw_listener_state_cancelled: {
461+ strongSelf.isListening = NO ;
462+ break ;
463+ }
464+ default :
465+ break ;
466+ }
467+ });
468+
469+ nw_listener_set_new_connection_handler (listener, ^(nw_connection_t newConnection) {
470+ __strong typeof (weakSelf) strongSelf = weakSelf;
471+ if (!strongSelf) return ;
472+
473+ // See acceptOnInterface:port:error: for rationale on delegate sharing.
474+ GCDAsyncSocket *newSocket = [[GCDAsyncSocket alloc ] initWithDelegate: strongSelf.delegate
475+ delegateQueue: strongSelf.delegateQueue
476+ socketQueue: nil ];
477+ newSocket.connection = newConnection;
478+
479+ nw_connection_set_state_changed_handler (newConnection, ^(nw_connection_state_t state, nw_error_t _Nullable error) {
480+ [newSocket handleStateChange: state error: error];
481+ });
482+
483+ nw_connection_set_queue (newConnection, newSocket.socketQueue );
484+ nw_connection_start (newConnection);
485+
486+ dispatch_async (strongSelf.delegateQueue , ^{
487+ id delegate = strongSelf.delegate ;
488+ if ([delegate respondsToSelector: @selector (socket:didAcceptNewSocket: )]) {
489+ [delegate socket: strongSelf didAcceptNewSocket: newSocket];
490+ }
491+ });
492+ });
493+
494+ nw_listener_set_queue (listener, self.socketQueue );
495+ nw_listener_start (listener);
496+
497+ return YES ;
498+ #else
274499 if (errPtr) {
275500 *errPtr = [NSError errorWithDomain: GCDAsyncSocketErrorDomain
276- code: GCDAsyncSocketErrorInvalidParameter
277- userInfo: @{NSLocalizedDescriptionKey : @" acceptOnPort:error: is not supported in NWAsyncSocketObjC ." }];
501+ code: GCDAsyncSocketErrorConnectionFailed
502+ userInfo: @{NSLocalizedDescriptionKey : @" Network.framework is not available on this platform ." }];
278503 }
279504 return NO ;
505+ #endif
280506}
281507
282508- (BOOL )connectToHost : (NSString *)host
@@ -362,7 +588,18 @@ - (BOOL)connectToHost:(NSString *)host
362588- (void )disconnect {
363589 __weak typeof (self) weakSelf = self;
364590 dispatch_async (self.socketQueue , ^{
365- [weakSelf disconnectInternalWithError: nil ];
591+ __strong typeof (weakSelf) strongSelf = weakSelf;
592+ if (!strongSelf) return ;
593+ #if NW_FRAMEWORK_AVAILABLE
594+ // Stop listener if in server mode
595+ if (strongSelf.listener ) {
596+ nw_listener_cancel (strongSelf.listener );
597+ strongSelf.listener = nil ;
598+ strongSelf.isListening = NO ;
599+ strongSelf.localPort = 0 ;
600+ }
601+ #endif
602+ [strongSelf disconnectInternalWithError: nil ];
366603 });
367604}
368605
@@ -513,10 +750,8 @@ - (void)handleStateChange:(nw_connection_state_t)state error:(nw_error_t _Nullab
513750 if (localHostStr) {
514751 self.localHost = [NSString stringWithUTF8String: localHostStr];
515752 }
516- const char *localPortStr = nw_endpoint_get_port (localEndpoint);
517- if (localPortStr) {
518- self.localPort = (uint16_t )strtoul (localPortStr, NULL , 10 );
519- }
753+ uint16_t localPortValue = nw_endpoint_get_port (localEndpoint);
754+ self.localPort = localPortValue;
520755 }
521756 }
522757#endif
@@ -720,9 +955,9 @@ - (void)disconnectWithError:(NSError *)error {
720955}
721956
722957- (void )disconnectInternalWithError : (NSError *)error {
723- if (!self.isConnected
958+ if (!self.isConnected && !self. isListening
724959#if NW_FRAMEWORK_AVAILABLE
725- && !self.connection
960+ && !self.connection && !self. listener
726961#endif
727962 ) {
728963 return ;
@@ -732,6 +967,11 @@ - (void)disconnectInternalWithError:(NSError *)error {
732967 self.isReadingContinuously = NO ;
733968
734969#if NW_FRAMEWORK_AVAILABLE
970+ if (self.listener ) {
971+ nw_listener_cancel (self.listener );
972+ self.listener = nil ;
973+ self.isListening = NO ;
974+ }
735975 if (self.connection ) {
736976 nw_connection_cancel (self.connection );
737977 self.connection = nil ;
0 commit comments