#import "PTChannel.h" #import "PTPrivate.h" #include #include #include #include #include #import // Read member of sockaddr_in without knowing the family #define PT_SOCKADDR_ACCESS(ss, member4, member6) \ (((ss)->ss_family == AF_INET) ? ( \ ((const struct sockaddr_in *)(ss))->member4 \ ) : ( \ ((const struct sockaddr_in6 *)(ss))->member6 \ )) // Connection state (storage: uint8_t) #define kConnStateNone 0 #define kConnStateConnecting 1 #define kConnStateConnected 2 #define kConnStateListening 3 // Delegate support optimization (storage: uint8_t) #define kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize 1 #define kDelegateFlagImplements_ioFrameChannel_didEndWithError 2 #define kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress 4 #pragma mark - // Note: We are careful about the size of this struct as each connected peer // implies one allocation of this struct. @interface PTChannel () { dispatch_io_t dispatchObj_channel_; dispatch_source_t dispatchObj_source_; NSError *endError_; // 64 bit @public // here be hacks id delegate_; // 64 bit uint8_t delegateFlags_; // 8 bit @private uint8_t connState_; // 8 bit //char padding_[6]; // 48 bit -- only if allocation speed is important } - (id)initWithProtocol:(PTProtocol*)protocol delegate:(id)delegate; - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD; @end static const uint8_t kUserInfoKey; #pragma mark - @interface PTData () - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length; @end #pragma mark - @interface PTAddress () { struct sockaddr_storage sockaddr_; } - (id)initWithSockaddr:(const struct sockaddr_storage*)addr; @end #pragma mark - @implementation PTChannel @synthesize protocol = protocol_; + (PTChannel*)channelWithDelegate:(id)delegate { return [[PTChannel alloc] initWithProtocol:[PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()] delegate:delegate]; } - (id)initWithProtocol:(PTProtocol*)protocol delegate:(id)delegate { if (!(self = [super init])) return nil; protocol_ = protocol; self.delegate = delegate; return self; } - (id)initWithProtocol:(PTProtocol*)protocol { if (!(self = [super init])) return nil; protocol_ = protocol; return self; } - (id)init { return [self initWithProtocol:[PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()]]; } - (void)dealloc { #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_channel_) dispatch_release(dispatchObj_channel_); else if (dispatchObj_source_) dispatch_release(dispatchObj_source_); #endif } - (BOOL)isConnected { return connState_ == kConnStateConnecting || connState_ == kConnStateConnected; } - (BOOL)isListening { return connState_ == kConnStateListening; } - (id)userInfo { return objc_getAssociatedObject(self, (void*)&kUserInfoKey); } - (void)setUserInfo:(id)userInfo { objc_setAssociatedObject(self, (const void*)&kUserInfoKey, userInfo, OBJC_ASSOCIATION_RETAIN); } - (void)setConnState:(char)connState { connState_ = connState; } - (void)setDispatchChannel:(dispatch_io_t)channel { assert(connState_ == kConnStateConnecting || connState_ == kConnStateConnected || connState_ == kConnStateNone); dispatch_io_t prevChannel = dispatchObj_channel_; if (prevChannel != channel) { dispatchObj_channel_ = channel; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_channel_) dispatch_retain(dispatchObj_channel_); if (prevChannel) dispatch_release(prevChannel); #endif if (!dispatchObj_channel_ && !dispatchObj_source_) { connState_ = kConnStateNone; } } } - (void)setDispatchSource:(dispatch_source_t)source { assert(connState_ == kConnStateListening || connState_ == kConnStateNone); dispatch_source_t prevSource = dispatchObj_source_; if (prevSource != source) { dispatchObj_source_ = source; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchObj_source_) dispatch_retain(dispatchObj_source_); if (prevSource) dispatch_release(prevSource); #endif if (!dispatchObj_channel_ && !dispatchObj_source_) { connState_ = kConnStateNone; } } } - (id)delegate { return delegate_; } - (void)setDelegate:(id)delegate { delegate_ = delegate; delegateFlags_ = 0; if (!delegate_) { return; } if ([delegate respondsToSelector:@selector(ioFrameChannel:shouldAcceptFrameOfType:tag:payloadSize:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize; } if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didEndWithError:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didEndWithError; } if (delegate_ && [delegate respondsToSelector:@selector(ioFrameChannel:didAcceptConnection:fromAddress:)]) { delegateFlags_ |= kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress; } } //- (void)setFileDescriptor:(dispatch_fd_t)fd { // [self setDispatchChannel:dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { // close(fd); // })]; //} #pragma mark - Connecting - (void)connectToPort:(int)port overUSBHub:(PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback { assert(protocol_ != NULL); if (connState_ != kConnStateNone) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); return; } connState_ = kConnStateConnecting; [usbHub connectToDevice:deviceID port:port onStart:^(NSError *err, dispatch_io_t dispatchChannel) { NSError *error = err; if (!error) { [self startReadingFromConnectedChannel:dispatchChannel error:&error]; } else { connState_ = kConnStateNone; } if (callback) callback(error); } onEnd:^(NSError *error) { if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { [delegate_ ioFrameChannel:self didEndWithError:error]; } endError_ = nil; }]; } - (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, PTAddress *address))callback { assert(protocol_ != NULL); if (connState_ != kConnStateNone) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil], nil); return; } connState_ = kConnStateConnecting; int error = 0; // Create socket dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket(AF_INET, SOCK_STREAM, 0) failed"); error = errno; if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil], nil); return; } // Connect socket struct sockaddr_in addr; bzero((char *)&addr, sizeof(addr)); addr.sin_len = sizeof(addr); addr.sin_family = AF_INET; addr.sin_port = htons(port); //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = htonl(address); // prevent SIGPIPE int on = 1; setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); // int socket, const struct sockaddr *address, socklen_t address_len if (connect(fd, (const struct sockaddr *)&addr, addr.sin_len) == -1) { //perror("connect"); error = errno; close(fd); if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); return; } // get actual address //if (getsockname(fd, (struct sockaddr*)&addr, (socklen_t*)&addr.sin_len) == -1) { // error = errno; // close(fd); // if (callback) callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil); // return; //} dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, fd, protocol_.queue, ^(int error) { close(fd); if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { NSError *err = error == 0 ? endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; [delegate_ ioFrameChannel:self didEndWithError:err]; endError_ = nil; } }); if (!dispatchChannel) { close(fd); if (callback) callback([[NSError alloc] initWithDomain:@"PTError" code:0 userInfo:nil], nil); return; } // Success NSError *err = nil; PTAddress *ptAddr = [[PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; [self startReadingFromConnectedChannel:dispatchChannel error:&err]; if (callback) callback(err, ptAddr); } #pragma mark - Listening and serving - (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback { assert(dispatchObj_source_ == nil); // Create socket dispatch_fd_t fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } // Connect socket struct sockaddr_in addr; bzero((char *)&addr, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(port); //addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); //addr.sin_addr.s_addr = htonl(INADDR_ANY); addr.sin_addr.s_addr = htonl(address); socklen_t socklen = sizeof(addr); int on = 1; if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (bind(fd, (struct sockaddr*)&addr, socklen) != 0) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } if (listen(fd, 512) != 0) { close(fd); if (callback) callback([NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil]); return; } [self setDispatchSource:dispatch_source_create(DISPATCH_SOURCE_TYPE_READ, fd, 0, protocol_.queue)]; dispatch_source_set_event_handler(dispatchObj_source_, ^{ unsigned long nconns = dispatch_source_get_data(dispatchObj_source_); while ([self acceptIncomingConnection:fd] && --nconns); }); dispatch_source_set_cancel_handler(dispatchObj_source_, ^{ // Captures *self*, effectively holding a reference to *self* until cancelled. dispatchObj_source_ = nil; close(fd); if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { [delegate_ ioFrameChannel:self didEndWithError:endError_]; endError_ = nil; } }); dispatch_resume(dispatchObj_source_); //NSLog(@"%@ opened on fd #%d", self, fd); connState_ = kConnStateListening; if (callback) callback(nil); } - (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD { struct sockaddr_in addr; socklen_t addrLen = sizeof(addr); dispatch_fd_t clientSocketFD = accept(serverSocketFD, (struct sockaddr*)&addr, &addrLen); if (clientSocketFD == -1) { perror("accept()"); return NO; } // prevent SIGPIPE int on = 1; setsockopt(clientSocketFD, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on)); if (fcntl(clientSocketFD, F_SETFL, O_NONBLOCK) == -1) { perror("fcntl(.. O_NONBLOCK)"); close(clientSocketFD); return NO; } if (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didAcceptConnection_fromAddress) { PTChannel *peerChannel = [[PTChannel alloc] initWithProtocol:protocol_ delegate:delegate_]; __block PTChannel *localChannelRef = self; dispatch_io_t dispatchChannel = dispatch_io_create(DISPATCH_IO_STREAM, clientSocketFD, protocol_.queue, ^(int error) { // Important note: This block captures *self*, thus a reference is held to // *self* until the fd is truly closed. localChannelRef = nil; close(clientSocketFD); if (peerChannel->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) { NSError *err = error == 0 ? peerChannel->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]; [peerChannel->delegate_ ioFrameChannel:peerChannel didEndWithError:err]; peerChannel->endError_ = nil; } }); [peerChannel setConnState:kConnStateConnected]; [peerChannel setDispatchChannel:dispatchChannel]; assert(((struct sockaddr_storage*)&addr)->ss_len == addrLen); PTAddress *address = [[PTAddress alloc] initWithSockaddr:(struct sockaddr_storage*)&addr]; [delegate_ ioFrameChannel:self didAcceptConnection:peerChannel fromAddress:address]; NSError *err = nil; if (![peerChannel startReadingFromConnectedChannel:dispatchChannel error:&err]) { NSLog(@"startReadingFromConnectedChannel failed in accept: %@", err); } } else { close(clientSocketFD); } return YES; } #pragma mark - Closing the channel - (void)close { if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { dispatch_io_close(dispatchObj_channel_, DISPATCH_IO_STOP); [self setDispatchChannel:NULL]; } else if (connState_ == kConnStateListening && dispatchObj_source_) { dispatch_source_cancel(dispatchObj_source_); } } - (void)cancel { if ((connState_ == kConnStateConnecting || connState_ == kConnStateConnected) && dispatchObj_channel_) { dispatch_io_close(dispatchObj_channel_, 0); [self setDispatchChannel:NULL]; } else if (connState_ == kConnStateListening && dispatchObj_source_) { dispatch_source_cancel(dispatchObj_source_); } } #pragma mark - Reading - (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error { if (connState_ != kConnStateNone && connState_ != kConnStateConnecting && connState_ != kConnStateConnected) { if (error) *error = [NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]; return NO; } if (dispatchObj_channel_ != channel) { [self close]; [self setDispatchChannel:channel]; } connState_ = kConnStateConnected; // helper BOOL(^handleError)(NSError*,BOOL) = ^BOOL(NSError *error, BOOL isEOS) { if (error) { //NSLog(@"Error while communicating: %@", error); endError_ = error; [self close]; return YES; } else if (isEOS) { [self cancel]; return YES; } return NO; }; [protocol_ readFramesOverChannel:channel onFrame:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize, dispatch_block_t resumeReadingFrames) { if (handleError(error, type == PTFrameTypeEndOfStream)) { return; } BOOL accepted = (channel == dispatchObj_channel_); if (accepted && (delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize)) { accepted = [delegate_ ioFrameChannel:self shouldAcceptFrameOfType:type tag:tag payloadSize:payloadSize]; } if (payloadSize == 0) { if (accepted && delegate_) { [delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:nil]; } else { // simply ignore the frame } resumeReadingFrames(); } else { // has payload if (!accepted) { // Read and discard payload, ignoring frame [protocol_ readAndDiscardDataOfSize:payloadSize overChannel:channel callback:^(NSError *error, BOOL endOfStream) { if (!handleError(error, endOfStream)) { resumeReadingFrames(); } }]; } else { [protocol_ readPayloadOfSize:payloadSize overChannel:channel callback:^(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize) { if (handleError(error, bufferSize == 0)) { return; } if (delegate_) { PTData *payload = [[PTData alloc] initWithMappedDispatchData:contiguousData data:(void*)buffer length:bufferSize]; [delegate_ ioFrameChannel:self didReceiveFrameOfType:type tag:tag payload:payload]; } resumeReadingFrames(); }]; } } }]; return YES; } #pragma mark - Sending - (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback { if (connState_ == kConnStateConnecting || connState_ == kConnStateConnected) { [protocol_ sendFrameOfType:frameType tag:tag withPayload:payload overChannel:dispatchObj_channel_ callback:callback]; } else if (callback) { callback([NSError errorWithDomain:NSPOSIXErrorDomain code:EPERM userInfo:nil]); } } #pragma mark - NSObject - (NSString*)description { id userInfo = objc_getAssociatedObject(self, (void*)&kUserInfoKey); return [NSString stringWithFormat:@"", self, ( connState_ == kConnStateConnecting ? @"connecting" : connState_ == kConnStateConnected ? @"connected" : connState_ == kConnStateListening ? @"listening" : @"closed"), userInfo ? " " : "", userInfo ? userInfo : @""]; } @end #pragma mark - @implementation PTAddress - (id)initWithSockaddr:(const struct sockaddr_storage*)addr { if (!(self = [super init])) return nil; assert(addr); memcpy((void*)&sockaddr_, (const void*)addr, addr->ss_len); return self; } - (NSString*)name { if (sockaddr_.ss_len) { const void *sin_addr = NULL; size_t bufsize = 0; if (sockaddr_.ss_family == AF_INET6) { bufsize = INET6_ADDRSTRLEN; sin_addr = (const void *)&((const struct sockaddr_in6*)&sockaddr_)->sin6_addr; } else { bufsize = INET_ADDRSTRLEN; sin_addr = (const void *)&((const struct sockaddr_in*)&sockaddr_)->sin_addr; } char *buf = CFAllocatorAllocate(kCFAllocatorDefault, bufsize+1, 0); if (inet_ntop(sockaddr_.ss_family, sin_addr, buf, (unsigned int)bufsize-1) == NULL) { CFAllocatorDeallocate(kCFAllocatorDefault, buf); return nil; } return [[NSString alloc] initWithBytesNoCopy:(void*)buf length:strlen(buf) encoding:NSUTF8StringEncoding freeWhenDone:YES]; } else { return nil; } } - (NSInteger)port { if (sockaddr_.ss_len) { return ntohs(PT_SOCKADDR_ACCESS(&sockaddr_, sin_port, sin6_port)); } else { return 0; } } - (NSString*)description { if (sockaddr_.ss_len) { return [NSString stringWithFormat:@"%@:%u", self.name, (unsigned)self.port]; } else { return @"(?)"; } } @end #pragma mark - @implementation PTData @synthesize dispatchData = dispatchData_; @synthesize data = data_; @synthesize length = length_; - (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length { if (!(self = [super init])) return nil; dispatchData_ = mappedContiguousData; #if PT_DISPATCH_RETAIN_RELEASE if (dispatchData_) dispatch_retain(dispatchData_); #endif data_ = data; length_ = length; return self; } - (void)dealloc { #if PT_DISPATCH_RETAIN_RELEASE if (dispatchData_) dispatch_release(dispatchData_); #endif data_ = NULL; length_ = 0; } #pragma mark - NSObject - (NSString*)description { return [NSString stringWithFormat:@"", self, length_]; } @end