Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save beccadax/5369105 to your computer and use it in GitHub Desktop.
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.
//
// 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
//
// 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