Created
April 12, 2013 03:28
-
-
Save beccadax/5369105 to your computer and use it in GitHub Desktop.
Subclass of NSPortNameServer that uses distributed notifications (in a sandbox-friendly way) to implement a fast name server for socket ports. Decent demonstration of NSDistributedNotificationCenter, stupid run loop tricks, and SecTransform.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TsDistributedNotificationPortNameServer.h | |
// Typesetter | |
// | |
// Created by Brent Royal-Gordon on 4/10/13. | |
// Copyright (c) 2013 Groundbreaking Software. All rights reserved. | |
// | |
#import <Foundation/Foundation.h> | |
@interface TsDistributedNotificationPortNameServer : NSPortNameServer | |
+ (TsDistributedNotificationPortNameServer*)sharedServer; | |
- (NSSocketPort *)portForName:(NSString *)name; | |
- (NSSocketPort *)portForName:(NSString *)name host:(NSString *)host; | |
- (BOOL)registerPort:(NSSocketPort *)port name:(NSString *)name; | |
- (BOOL)removePortForName:(NSString *)name; | |
@end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// TsDistributedNotificationPortNameServer.m | |
// Typesetter | |
// | |
// Created by Brent Royal-Gordon on 4/10/13. | |
// Copyright (c) 2013 Groundbreaking Software. All rights reserved. | |
// | |
#import "TsDistributedNotificationPortNameServer.h" | |
static NSString * const TsDistributedNotificationPortNameServerRequestNotificationPrefix = @"com.groundbreakingsoftware.typesetter.TsDistributedNotificationPortNameServerRequest "; | |
static NSString * const TsDistributedNotificationPortNameServerResponseNotificationPrefix = @"com.groundbreakingsoftware.typesetter.TsDistributedNotificationPortNameServerResponse "; | |
static CFStringRef const kTsDistributedNotificationPortNameServerWaitingForResponseMode = CFSTR("TsDistributedNotificationPortNameServerWaitingForResponseMode"); | |
@interface TsDistributedNotificationPortNameServer () | |
@property (readonly,strong) NSMapTable * registeredPorts; | |
@property (readonly,strong) NSMutableDictionary * responsePorts; | |
@property (readonly,strong) NSMutableDictionary * responseRunLoops; | |
@end | |
@implementation TsDistributedNotificationPortNameServer | |
+ (void)initialize { | |
} | |
- (id)init { | |
if(!_registeredPorts && (self = [super init])) { | |
_registeredPorts = [NSMapTable strongToWeakObjectsMapTable]; | |
_responsePorts = [NSMutableDictionary new]; | |
_responseRunLoops = [NSMutableDictionary new]; | |
// NSDistributedNotificationCenter only receives notifications when the run loop is in a common mode. | |
CFRunLoopAddCommonMode(CFRunLoopGetMain(), kTsDistributedNotificationPortNameServerWaitingForResponseMode); | |
} | |
return self; | |
} | |
+ (TsDistributedNotificationPortNameServer *)sharedServer { | |
static TsDistributedNotificationPortNameServer * singleton; | |
static dispatch_once_t once; | |
dispatch_once(&once, ^{ | |
singleton = [TsDistributedNotificationPortNameServer new]; | |
}); | |
return singleton; | |
} | |
- (BOOL)registerPort:(NSSocketPort *)port name:(NSString *)name { | |
if([self portForName:name]) { | |
return NO; | |
} | |
// XXX There are race conditions possible. These *probably* shouldn't matter for my purposes. I hope. | |
[self.registeredPorts setObject:port forKey:name]; | |
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(requested:) name:[TsDistributedNotificationPortNameServerRequestNotificationPrefix stringByAppendingString:name] object:nil]; | |
return YES; | |
} | |
- (BOOL)removePortForName:(NSString *)name { | |
[NSDistributedNotificationCenter.defaultCenter removeObserver:self name:[TsDistributedNotificationPortNameServerRequestNotificationPrefix stringByAppendingString:name] object:nil]; | |
[self.registeredPorts removeObjectForKey:name]; | |
return YES; | |
} | |
- (NSSocketPort *)portForName:(NSString *)name { | |
NSString * uuid = [NSUUID new].UUIDString; | |
[NSDistributedNotificationCenter.defaultCenter addObserver:self selector:@selector(responded:) name:[TsDistributedNotificationPortNameServerResponseNotificationPrefix stringByAppendingString:uuid] object:nil]; | |
[NSDistributedNotificationCenter.defaultCenter postNotificationName:[TsDistributedNotificationPortNameServerRequestNotificationPrefix stringByAppendingString:name] object:uuid userInfo:nil deliverImmediately:YES]; | |
self.responseRunLoops[uuid] = (__bridge id)CFRunLoopGetCurrent(); | |
CFRunLoopRunInMode(kTsDistributedNotificationPortNameServerWaitingForResponseMode, 1, false); | |
[self.responseRunLoops removeObjectForKey:uuid]; | |
[NSDistributedNotificationCenter.defaultCenter removeObserver:self name:[TsDistributedNotificationPortNameServerResponseNotificationPrefix stringByAppendingString:uuid] object:nil]; | |
if(!self.responsePorts[uuid]) { | |
return nil; | |
} | |
NSSocketPort * port = self.responsePorts[uuid]; | |
[self.responsePorts removeObjectForKey:uuid]; | |
return port; | |
} | |
- (NSSocketPort *)portForName:(NSString *)name host:(NSString *)host { | |
NSParameterAssert(host == nil || host.length == 0); | |
return [self portForName:name]; | |
} | |
- (void)requested:(NSNotification*)note { | |
NSString * name = [note.name stringByReplacingCharactersInRange:NSMakeRange(0, TsDistributedNotificationPortNameServerRequestNotificationPrefix.length) withString:@""]; | |
NSSocketPort * port = [self.registeredPorts objectForKey:name]; | |
if(!port) { | |
return; | |
} | |
SecTransformRef encoder = SecEncodeTransformCreate(kSecBase64Encoding, NULL); | |
if (!encoder) { | |
return; | |
} | |
if(!SecTransformSetAttribute(encoder, kSecTransformInputAttributeName, (__bridge CFDataRef)port.address, NULL)) { | |
CFRelease(encoder); | |
return; | |
} | |
NSData * encodedAddressData = (__bridge_transfer NSData*)SecTransformExecute(encoder, NULL); | |
CFRelease(encoder); | |
if (!encodedAddressData) { | |
return; | |
} | |
NSString * encodedAddress = [[NSString alloc] initWithData:encodedAddressData encoding:NSASCIIStringEncoding]; | |
NSDictionary * dict = @{ @"protocolFamily": @(port.protocolFamily), @"socketType": @(port.socketType), @"protocol": @(port.protocol), @"address": encodedAddress }; | |
NSData * jsonData = [NSJSONSerialization dataWithJSONObject:dict options:0 error:NULL]; | |
if(!jsonData) { | |
return; | |
} | |
NSString * json = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding]; | |
[NSDistributedNotificationCenter.defaultCenter postNotificationName:[TsDistributedNotificationPortNameServerResponseNotificationPrefix stringByAppendingString:note.object] object:json userInfo:nil deliverImmediately:YES]; | |
} | |
- (void)responded:(NSNotification*)note { | |
NSString * requestID = [note.name stringByReplacingCharactersInRange:NSMakeRange(0, TsDistributedNotificationPortNameServerResponseNotificationPrefix.length) withString:@""]; | |
NSData * jsonData = [note.object dataUsingEncoding:NSUTF8StringEncoding]; | |
NSDictionary * json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:NULL]; | |
if(!json) { | |
return; | |
} | |
CFRunLoopRef runLoop = (__bridge CFRunLoopRef)self.responseRunLoops[requestID]; | |
if(!runLoop || ![(__bridge_transfer NSString*)CFRunLoopCopyCurrentMode(runLoop) isEqualToString:(__bridge NSString*)kTsDistributedNotificationPortNameServerWaitingForResponseMode]) { | |
return; | |
} | |
SecTransformRef decoder = SecDecodeTransformCreate(kSecBase64Encoding, NULL); | |
if (!decoder) { | |
return; | |
} | |
if(!SecTransformSetAttribute(decoder, kSecTransformInputAttributeName, (__bridge CFDataRef)[json[@"address"] dataUsingEncoding:NSASCIIStringEncoding], NULL)) { | |
CFRelease(decoder); | |
return; | |
} | |
NSData * decodedAddressData = (__bridge_transfer NSData*)SecTransformExecute(decoder, NULL); | |
CFRelease(decoder); | |
if (!decodedAddressData) { | |
return; | |
} | |
NSSocketPort * port = [[NSSocketPort alloc] initRemoteWithProtocolFamily:[json[@"protocolFamily"] intValue] socketType:[json[@"socketType"] intValue] protocol:[json[@"protocol"] intValue] address:decodedAddressData]; | |
self.responsePorts[requestID] = port; | |
CFRunLoopStop(runLoop); | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment