This commit is contained in:
2025-10-28 10:18:10 +08:00
parent efb04d134e
commit 1deca2ae5b
166 changed files with 17288 additions and 1427 deletions

View File

@@ -0,0 +1,136 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Represents a communication channel between two endpoints talking the same
// Lookin_PTProtocol.
//
#import <Foundation/Foundation.h>
#import <dispatch/dispatch.h>
#import <netinet/in.h>
#import <sys/socket.h>
#import "Lookin_PTProtocol.h"
#import "Lookin_PTUSBHub.h"
@class Lookin_PTData, Lookin_PTAddress;
@protocol Lookin_PTChannelDelegate;
@interface Lookin_PTChannel : NSObject
// Delegate
@property (strong) id<Lookin_PTChannelDelegate> delegate;
// Communication protocol. Must not be nil.
@property Lookin_PTProtocol *protocol;
// YES if this channel is a listening server
@property (readonly) BOOL isListening;
// YES if this channel is a connected peer
@property (readonly) BOOL isConnected;
// Arbitrary attachment. Note that if you set this, the object will grow by
// 8 bytes (64 bits).
@property (strong) id userInfo;
@property(nonatomic, assign) int uniqueID;
@property(nonatomic, assign) NSInteger targetPort;
- (NSString *)debugTag;
// Create a new channel using the shared Lookin_PTProtocol for the current dispatch
// queue, with *delegate*.
+ (Lookin_PTChannel*)channelWithDelegate:(id<Lookin_PTChannelDelegate>)delegate;
// Initialize a new frame channel, configuring it to use the calling queue's
// protocol instance (as returned by [Lookin_PTProtocol sharedProtocolForQueue:
// dispatch_get_current_queue()])
- (id)init;
// Initialize a new frame channel with a specific protocol.
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol;
// Initialize a new frame channel with a specific protocol and delegate.
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate;
// Connect to a TCP port on a device connected over USB
- (void)connectToPort:(int)port overUSBHub:(Lookin_PTUSBHub*)usbHub deviceID:(NSNumber*)deviceID callback:(void(^)(NSError *error))callback;
// Connect to a TCP port at IPv4 address. Provided port must NOT be in network
// byte order. Provided in_addr_t must NOT be in network byte order. A value returned
// from inet_aton() will be in network byte order. You can use a value of inet_aton()
// as the address parameter here, but you must flip the byte order before passing the
// in_addr_t to this function.
- (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_PTAddress *address))callback;
// Listen for connections on port and address, effectively starting a socket
// server. Provided port must NOT be in network byte order. Provided in_addr_t
// must NOT be in network byte order.
// For this to make sense, you should provide a onAccept block handler
// or a delegate implementing ioFrameChannel:didAcceptConnection:.
- (void)listenOnPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error))callback;
// Send a frame with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload callback:(void(^)(NSError *error))callback;
// Lower-level method to assign a connected dispatch IO channel to this channel
- (BOOL)startReadingFromConnectedChannel:(dispatch_io_t)channel error:(__autoreleasing NSError**)error;
// Close the channel, preventing further reading and writing. Any ongoing and
// queued reads and writes will be aborted.
- (void)close;
// "graceful" close -- any ongoing and queued reads and writes will complete
// before the channel ends.
- (void)cancel;
@end
// Wraps a mapped dispatch_data_t object. The memory pointed to by *data* is
// valid until *dispatchData* is deallocated (normally when the receiver is
// deallocated).
@interface Lookin_PTData : NSObject
@property (readonly) dispatch_data_t dispatchData;
@property (readonly) void *data;
@property (readonly) size_t length;
@end
// Represents a peer's address
@interface Lookin_PTAddress : NSObject
// For network addresses, this is the IP address in textual format
@property (readonly) NSString *name;
// For network addresses, this is the port number. Otherwise 0 (zero).
@property (readonly) NSInteger port;
@end
// Protocol for Lookin_PTChannel delegates
@protocol Lookin_PTChannelDelegate <NSObject>
@required
// Invoked when a new frame has arrived on a channel.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didReceiveFrameOfType:(uint32_t)type tag:(uint32_t)tag payload:(Lookin_PTData*)payload;
@optional
// Invoked to accept an incoming frame on a channel. Reply NO ignore the
// incoming frame. If not implemented by the delegate, all frames are accepted.
- (BOOL)ioFrameChannel:(Lookin_PTChannel*)channel shouldAcceptFrameOfType:(uint32_t)type tag:(uint32_t)tag payloadSize:(uint32_t)payloadSize;
// Invoked when the channel closed. If it closed because of an error, *error* is
// a non-nil NSError object.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didEndWithError:(NSError*)error;
// For listening channels, this method is invoked when a new connection has been
// accepted.
- (void)ioFrameChannel:(Lookin_PTChannel*)channel didAcceptConnection:(Lookin_PTChannel*)otherChannel fromAddress:(Lookin_PTAddress*)address;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,675 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTChannel.h"
#import "Lookin_PTPrivate.h"
#include <sys/ioctl.h>
#include <sys/un.h>
#include <err.h>
#include <fcntl.h>
#include <arpa/inet.h>
#import <objc/runtime.h>
// 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
static int ChannelInstanceCount = 0;
static int ChannelUniqueID = 0;
#pragma mark -
// Note: We are careful about the size of this struct as each connected peer
// implies one allocation of this struct.
@interface Lookin_PTChannel () {
dispatch_io_t dispatchObj_channel_;
dispatch_source_t dispatchObj_source_;
NSError *endError_; // 64 bit
@public // here be hacks
id<Lookin_PTChannelDelegate> 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:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate;
- (BOOL)acceptIncomingConnection:(dispatch_fd_t)serverSocketFD;
@end
static const uint8_t kUserInfoKey;
#pragma mark -
@interface Lookin_PTData ()
- (id)initWithMappedDispatchData:(dispatch_data_t)mappedContiguousData data:(void*)data length:(size_t)length;
@end
#pragma mark -
@interface Lookin_PTAddress () {
struct sockaddr_storage sockaddr_;
}
- (id)initWithSockaddr:(const struct sockaddr_storage*)addr;
@end
#pragma mark -
@implementation Lookin_PTChannel
@synthesize protocol = protocol_;
+ (Lookin_PTChannel*)channelWithDelegate:(id<Lookin_PTChannelDelegate>)delegate {
return [[Lookin_PTChannel alloc] initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()] delegate:delegate];
}
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol delegate:(id<Lookin_PTChannelDelegate>)delegate {
if (!(self = [super init])) return nil;
protocol_ = protocol;
self.delegate = delegate;
[self didInit];
return self;
}
- (id)initWithProtocol:(Lookin_PTProtocol*)protocol {
if (!(self = [super init])) return nil;
protocol_ = protocol;
[self didInit];
return self;
}
- (id)init {
[self didInit];
return [self initWithProtocol:[Lookin_PTProtocol sharedProtocolForQueue:dispatch_get_main_queue()]];
}
- (void)didInit {
ChannelUniqueID++;
ChannelInstanceCount++;
self.uniqueID = ChannelUniqueID;
// NSLog(@"LookinServer - Init channel(ID: %@). Total count: %@", @(self.uniqueID), @(ChannelInstanceCount));
}
- (void)dealloc {
ChannelInstanceCount--;
// NSLog(@"LookinServer - Dealloc channel%@. Still lives count: %@", self.debugTag, @(ChannelInstanceCount));
#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<Lookin_PTChannelDelegate>)delegate {
return delegate_;
}
- (void)setDelegate:(id<Lookin_PTChannelDelegate>)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;
}
}
- (NSString *)debugTag {
NSString *state = @"";
if (connState_ == kConnStateNone) {
state = @"None";
} else if (connState_ == kConnStateConnecting) {
state = @"Connecting";
} else if (connState_ == kConnStateConnected) {
state = @"Connected";
} else if (connState_ == kConnStateListening) {
state = @"Listening";
} else {
state = @"Undefined";
}
return [NSString stringWithFormat:@"[%@-%@,%@]", @(self.uniqueID), @(self.targetPort), state];
}
//- (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:(Lookin_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 {
self->connState_ = kConnStateNone;
}
if (callback) callback(error);
} onEnd:^(NSError *error) {
if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
[self->delegate_ ioFrameChannel:self didEndWithError:error];
}
self->endError_ = nil;
}];
}
- (void)connectToPort:(in_port_t)port IPv4Address:(in_addr_t)address callback:(void(^)(NSError *error, Lookin_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 (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
NSError *err = error == 0 ? self->endError_ : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil];
[self->delegate_ ioFrameChannel:self didEndWithError:err];
self->endError_ = nil;
}
});
if (!dispatchChannel) {
close(fd);
if (callback) callback([[NSError alloc] initWithDomain:@"PTError" code:0 userInfo:nil], nil);
return;
}
// Success
NSError *err = nil;
Lookin_PTAddress *ptAddr = [[Lookin_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(self->dispatchObj_source_);
while ([self acceptIncomingConnection:fd] && --nconns);
});
dispatch_source_set_cancel_handler(dispatchObj_source_, ^{
// Captures *self*, effectively holding a reference to *self* until cancelled.
self->dispatchObj_source_ = nil;
close(fd);
if (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_didEndWithError) {
[self->delegate_ ioFrameChannel:self didEndWithError:self->endError_];
self->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) {
Lookin_PTChannel *peerChannel = [[Lookin_PTChannel alloc] initWithProtocol:protocol_ delegate:delegate_];
__block Lookin_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);
Lookin_PTAddress *address = [[Lookin_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 {
// NSLog(@"LookinServer - Will close chanel: %@", self.debugTag);
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_);
}
}
/// Client Client Peertalk connect channel
- (void)cancel {
// NSLog(@"LookinServer - Will cancel chanel: %@", self.debugTag);
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);
self->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 == self->dispatchObj_channel_);
if (accepted && (self->delegateFlags_ & kDelegateFlagImplements_ioFrameChannel_shouldAcceptFrameOfType_tag_payloadSize)) {
accepted = [self->delegate_ ioFrameChannel:self shouldAcceptFrameOfType:type tag:tag payloadSize:payloadSize];
}
if (payloadSize == 0) {
if (accepted && self->delegate_) {
[self->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
[self->protocol_ readAndDiscardDataOfSize:payloadSize overChannel:channel callback:^(NSError *error, BOOL endOfStream) {
if (!handleError(error, endOfStream)) {
resumeReadingFrames();
}
}];
} else {
[self->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 (self->delegate_) {
Lookin_PTData *payload = [[Lookin_PTData alloc] initWithMappedDispatchData:contiguousData data:(void*)buffer length:bufferSize];
[self->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:@"<Lookin_PTChannel: %p (%@)%s%@>", self, ( connState_ == kConnStateConnecting ? @"connecting"
: connState_ == kConnStateConnected ? @"connected"
: connState_ == kConnStateListening ? @"listening"
: @"closed"),
userInfo ? " " : "", userInfo ? userInfo : @""];
}
@end
#pragma mark -
@implementation Lookin_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 Lookin_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:@"<Lookin_PTData: %p (%zu bytes)>", self, length_];
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,20 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && (!defined(__IPHONE_6_0) || __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_6_0)) || \
(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && (!defined(__MAC_10_8) || __MAC_OS_X_VERSION_MIN_REQUIRED < __MAC_10_8))
#define PT_DISPATCH_RETAIN_RELEASE 1
#else
#define PT_DISPATCH_RETAIN_RELEASE 0
#endif
#if PT_DISPATCH_RETAIN_RELEASE
#define PT_PRECISE_LIFETIME
#define PT_PRECISE_LIFETIME_UNUSED
#else
#define PT_PRECISE_LIFETIME __attribute__((objc_precise_lifetime))
#define PT_PRECISE_LIFETIME_UNUSED __attribute__((objc_precise_lifetime, unused))
#endif
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,122 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// A universal frame-based communication protocol which can be used to exchange
// arbitrary structured data.
//
// In short:
//
// - Each transmission is comprised by one fixed-size frame.
// - Each frame contains a protocol version number.
// - Each frame contains an application frame type.
// - Each frame can contain an identifying tag.
// - Each frame can have application-specific data of up to UINT32_MAX size.
// - Transactions style messaging can be modeled on top using frame tags.
// - Lightweight API on top of libdispatch (aka GCD) -- close to the metal.
//
#include <dispatch/dispatch.h>
#import <Foundation/Foundation.h>
// Special frame tag that signifies "no tag". Your implementation should never
// create a reply for a frame with this tag.
static const uint32_t PTFrameNoTag = 0;
// Special frame type that signifies that the stream has ended.
static const uint32_t PTFrameTypeEndOfStream = 0;
// NSError domain
FOUNDATION_EXPORT NSString * const Lookin_PTProtocolErrorDomain;
@interface Lookin_PTProtocol : NSObject
// Queue on which to run data processing blocks.
@property dispatch_queue_t queue;
// Get the shared protocol object for *queue*
+ (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue;
// Initialize a new protocol object to use a specific queue.
- (id)initWithDispatchQueue:(dispatch_queue_t)queue;
// Initialize a new protocol object to use the current calling queue.
- (id)init;
#pragma mark Sending frames
// Generate a new tag that is unique within this protocol object.
- (uint32_t)newTag;
// Send a frame over *channel* with an optional payload and optional callback.
// If *callback* is not NULL, the block is invoked when either an error occured
// or when the frame (and payload, if any) has been completely sent.
- (void)sendFrameOfType:(uint32_t)frameType
tag:(uint32_t)tag
withPayload:(dispatch_data_t)payload
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error))callback;
#pragma mark Receiving frames
// Read frames over *channel* as they arrive.
// The onFrame handler is responsible for reading (or discarding) any payload
// and call *resumeReadingFrames* afterwards to resume reading frames.
// To stop reading frames, simply do not invoke *resumeReadingFrames*.
// When the stream ends, a frame of type PTFrameTypeEndOfStream is received.
- (void)readFramesOverChannel:(dispatch_io_t)channel
onFrame:(void(^)(NSError *error,
uint32_t type,
uint32_t tag,
uint32_t payloadSize,
dispatch_block_t resumeReadingFrames))onFrame;
// Read a single frame over *channel*. A frame of type PTFrameTypeEndOfStream
// denotes the stream has ended.
- (void)readFrameOverChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error,
uint32_t frameType,
uint32_t frameTag,
uint32_t payloadSize))callback;
#pragma mark Receiving frame payloads
// Read a complete payload. It's the callers responsibility to make sure
// payloadSize is not too large since memory will be automatically allocated
// where only payloadSize is the limit.
// The returned dispatch_data_t object owns *buffer* and thus you need to call
// dispatch_retain on *contiguousData* if you plan to keep *buffer* around after
// returning from the callback.
- (void)readPayloadOfSize:(size_t)payloadSize
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error,
dispatch_data_t contiguousData,
const uint8_t *buffer,
size_t bufferSize))callback;
// Discard data of *size* waiting on *channel*. *callback* can be NULL.
- (void)readAndDiscardDataOfSize:(size_t)size
overChannel:(dispatch_io_t)channel
callback:(void(^)(NSError *error, BOOL endOfStream))callback;
@end
@interface NSData (Lookin_PTProtocol)
// Creates a new dispatch_data_t object which references the receiver and uses
// the receivers bytes as its backing data. The returned dispatch_data_t object
// holds a reference to the recevier. It's the callers responsibility to call
// dispatch_release on the returned object when done.
- (dispatch_data_t)createReferencingDispatchData;
+ (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data;
@end
@interface NSDictionary (Lookin_PTProtocol)
// See description of -[NSData(Lookin_PTProtocol) createReferencingDispatchData]
- (dispatch_data_t)createReferencingDispatchData;
// Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure.
+ (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,428 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTProtocol.h"
#import "Lookin_PTPrivate.h"
#import <objc/runtime.h>
static const uint32_t PTProtocolVersion1 = 1;
NSString * const Lookin_PTProtocolErrorDomain = @"PTProtocolError";
// This is what we send as the header for each frame.
typedef struct _PTFrame {
// The version of the frame and protocol.
uint32_t version;
// Type of frame
uint32_t type;
// Unless zero, a tag is retained in frames that are responses to previous
// frames. Applications can use this to build transactions or request-response
// logic.
uint32_t tag;
// If payloadSize is larger than zero, *payloadSize* number of bytes are
// following, constituting application-specific data.
uint32_t payloadSize;
} PTFrame;
@interface Lookin_PTProtocol () {
uint32_t nextFrameTag_;
@public
dispatch_queue_t queue_;
}
- (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload;
@end
static void _release_queue_local_protocol(void *objcobj) {
if (objcobj) {
Lookin_PTProtocol *protocol = (__bridge_transfer id)objcobj;
protocol->queue_ = NULL;
}
}
@interface Lookin_RQueueLocalIOFrameProtocol : Lookin_PTProtocol
@end
@implementation Lookin_RQueueLocalIOFrameProtocol
- (void)setQueue:(dispatch_queue_t)queue {
}
@end
@implementation Lookin_PTProtocol
+ (Lookin_PTProtocol*)sharedProtocolForQueue:(dispatch_queue_t)queue {
static const char currentQueueFrameProtocolKey;
//dispatch_queue_t queue = dispatch_get_current_queue();
Lookin_PTProtocol *currentQueueFrameProtocol = (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, &currentQueueFrameProtocolKey);
if (!currentQueueFrameProtocol) {
currentQueueFrameProtocol = [[Lookin_RQueueLocalIOFrameProtocol alloc] initWithDispatchQueue:NULL];
currentQueueFrameProtocol->queue_ = queue; // reference, no retain, since we would create cyclic references
dispatch_queue_set_specific(queue, &currentQueueFrameProtocolKey, (__bridge_retained void*)currentQueueFrameProtocol, &_release_queue_local_protocol);
return (__bridge Lookin_PTProtocol*)dispatch_queue_get_specific(queue, &currentQueueFrameProtocolKey); // to avoid race conds
} else {
return currentQueueFrameProtocol;
}
}
- (id)initWithDispatchQueue:(dispatch_queue_t)queue {
if (!(self = [super init])) return nil;
queue_ = queue;
#if PT_DISPATCH_RETAIN_RELEASE
if (queue_) dispatch_retain(queue_);
#endif
return self;
}
- (id)init {
return [self initWithDispatchQueue:dispatch_get_main_queue()];
}
- (void)dealloc {
if (queue_) {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(queue_);
#endif
}
}
- (dispatch_queue_t)queue {
return queue_;
}
- (void)setQueue:(dispatch_queue_t)queue {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_queue_t prev_queue = queue_;
queue_ = queue;
if (queue_) dispatch_retain(queue_);
if (prev_queue) dispatch_release(prev_queue);
#else
queue_ = queue;
#endif
}
- (uint32_t)newTag {
return ++nextFrameTag_;
}
#pragma mark -
#pragma mark Creating frames
- (dispatch_data_t)createDispatchDataWithFrameOfType:(uint32_t)type frameTag:(uint32_t)frameTag payload:(dispatch_data_t)payload {
PTFrame *frame = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(PTFrame), 0);
frame->version = htonl(PTProtocolVersion1);
frame->type = htonl(type);
frame->tag = htonl(frameTag);
if (payload) {
size_t payloadSize = dispatch_data_get_size(payload);
assert(payloadSize <= UINT32_MAX);
frame->payloadSize = htonl((uint32_t)payloadSize);
} else {
frame->payloadSize = 0;
}
dispatch_data_t frameData = dispatch_data_create((const void*)frame, sizeof(PTFrame), queue_, ^{
CFAllocatorDeallocate(kCFAllocatorDefault, (void*)frame);
});
if (payload && frame->payloadSize != 0) {
// chain frame + payload
dispatch_data_t data = dispatch_data_create_concat(frameData, payload);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(frameData);
#endif
frameData = data;
}
return frameData;
}
#pragma mark -
#pragma mark Sending frames
- (void)sendFrameOfType:(uint32_t)frameType tag:(uint32_t)tag withPayload:(dispatch_data_t)payload overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*))callback {
dispatch_data_t frame = [self createDispatchDataWithFrameOfType:frameType frameTag:tag payload:payload];
dispatch_io_write(channel, 0, frame, queue_, ^(bool done, dispatch_data_t data, int _errno) {
if (done && callback) {
callback(_errno == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil]);
}
});
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(frame);
#endif
}
#pragma mark -
#pragma mark Receiving frames
- (void)readFrameOverChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, uint32_t frameType, uint32_t frameTag, uint32_t payloadSize))callback {
__block dispatch_data_t allData = NULL;
dispatch_io_read(channel, 0, sizeof(PTFrame), queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error);
size_t dataSize = data ? dispatch_data_get_size(data) : 0;
if (dataSize) {
if (!allData) {
allData = data;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(allData);
#endif
} else {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_data_t allDataPrev = allData;
allData = dispatch_data_create_concat(allData, data);
dispatch_release(allDataPrev);
#else
allData = dispatch_data_create_concat(allData, data);
#endif
}
}
if (done) {
if (error != 0) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], 0, 0, 0);
return;
}
if (dataSize == 0) {
callback(nil, PTFrameTypeEndOfStream, 0, 0);
return;
}
if (!allData || dispatch_data_get_size(allData) < sizeof(PTFrame)) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0);
return;
}
PTFrame *frame = NULL;
size_t size = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(allData, (const void **)&frame, &size); // precise lifetime guarantees bytes in frame will stay valid till the end of scope
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(allData);
#endif
if (!contiguousData) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], 0, 0, 0);
return;
}
frame->version = ntohl(frame->version);
if (frame->version != PTProtocolVersion1) {
callback([[NSError alloc] initWithDomain:Lookin_PTProtocolErrorDomain code:0 userInfo:nil], 0, 0, 0);
} else {
frame->type = ntohl(frame->type);
frame->tag = ntohl(frame->tag);
frame->payloadSize = ntohl(frame->payloadSize);
callback(nil, frame->type, frame->tag, frame->payloadSize);
}
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
}
});
}
- (void)readPayloadOfSize:(size_t)payloadSize overChannel:(dispatch_io_t)channel callback:(void(^)(NSError *error, dispatch_data_t contiguousData, const uint8_t *buffer, size_t bufferSize))callback {
__block dispatch_data_t allData = NULL;
dispatch_io_read(channel, 0, payloadSize, queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read: done=%d data=%p error=%d", done, data, error);
size_t dataSize = dispatch_data_get_size(data);
if (dataSize) {
if (!allData) {
allData = data;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(allData);
#endif
} else {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_data_t allDataPrev = allData;
allData = dispatch_data_create_concat(allData, data);
dispatch_release(allDataPrev);
#else
allData = dispatch_data_create_concat(allData, data);
#endif
}
}
if (done) {
if (error != 0) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], NULL, NULL, 0);
return;
}
if (dataSize == 0) {
#if PT_DISPATCH_RETAIN_RELEASE
if (allData) dispatch_release(allData);
#endif
callback(nil, NULL, NULL, 0);
return;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = NULL;
if (allData) {
contiguousData = dispatch_data_create_map(allData, (const void **)&buffer, &bufferSize);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(allData); allData = NULL;
#endif
if (!contiguousData) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:ENOMEM userInfo:nil], NULL, NULL, 0);
return;
}
}
callback(nil, contiguousData, buffer, bufferSize);
#if PT_DISPATCH_RETAIN_RELEASE
if (contiguousData) dispatch_release(contiguousData);
#endif
}
});
}
- (void)readAndDiscardDataOfSize:(size_t)size overChannel:(dispatch_io_t)channel callback:(void(^)(NSError*, BOOL))callback {
dispatch_io_read(channel, 0, size, queue_, ^(bool done, dispatch_data_t data, int error) {
if (done && callback) {
size_t dataSize = data ? dispatch_data_get_size(data) : 0;
callback(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], dataSize == 0);
}
});
}
- (void)readFramesOverChannel:(dispatch_io_t)channel onFrame:(void(^)(NSError*, uint32_t, uint32_t, uint32_t, dispatch_block_t))onFrame {
[self readFrameOverChannel:channel callback:^(NSError *error, uint32_t type, uint32_t tag, uint32_t payloadSize) {
onFrame(error, type, tag, payloadSize, ^{
if (type != PTFrameTypeEndOfStream) {
[self readFramesOverChannel:channel onFrame:onFrame];
}
});
}];
}
@end
@interface Lookin_PTDispatchData : NSObject {
dispatch_data_t dispatchData_;
}
@end
@implementation Lookin_PTDispatchData
- (id)initWithDispatchData:(dispatch_data_t)dispatchData {
if (!(self = [super init])) return nil;
dispatchData_ = dispatchData;
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_retain(dispatchData_);
#endif
return self;
}
- (void)dealloc {
#if PT_DISPATCH_RETAIN_RELEASE
if (dispatchData_) dispatch_release(dispatchData_);
#endif
}
@end
@implementation NSData (Lookin_PTProtocol)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-getter-return-value"
- (dispatch_data_t)createReferencingDispatchData {
// Note: The queue is used to submit the destructor. Since we only perform an
// atomic release of self, it doesn't really matter which queue is used, thus
// we use the current calling queue.
return dispatch_data_create((const void*)self.bytes, self.length, dispatch_get_main_queue(), ^{
// trick to have the block capture the data, thus retain/releasing
[self length];
});
}
#pragma clang diagnostic pop
+ (NSData *)dataWithContentsOfDispatchData:(dispatch_data_t)data {
if (!data) {
return nil;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize);
if (!contiguousData) {
return nil;
}
Lookin_PTDispatchData *dispatchDataRef = [[Lookin_PTDispatchData alloc] initWithDispatchData:contiguousData];
NSData *newData = [NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO];
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
static const bool kDispatchDataRefKey;
objc_setAssociatedObject(newData, (const void*)kDispatchDataRefKey, dispatchDataRef, OBJC_ASSOCIATION_RETAIN);
return newData;
}
@end
@implementation NSDictionary (Lookin_PTProtocol)
- (dispatch_data_t)createReferencingDispatchData {
NSError *error = nil;
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:self format:NSPropertyListBinaryFormat_v1_0 options:0 error:&error];
if (!plistData) {
NSLog(@"Failed to serialize property list: %@", error);
return nil;
} else {
return [plistData createReferencingDispatchData];
}
}
// Decode *data* as a peroperty list-encoded dictionary. Returns nil on failure.
+ (NSDictionary*)dictionaryWithContentsOfDispatchData:(dispatch_data_t)data {
if (!data) {
return nil;
}
uint8_t *buffer = NULL;
size_t bufferSize = 0;
PT_PRECISE_LIFETIME dispatch_data_t contiguousData = dispatch_data_create_map(data, (const void **)&buffer, &bufferSize);
if (!contiguousData) {
return nil;
}
NSDictionary *dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:(void*)buffer length:bufferSize freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:nil];
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(contiguousData);
#endif
return dict;
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,88 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#include <dispatch/dispatch.h>
#import <Foundation/Foundation.h>
// Lookin_PTUSBDeviceDidAttachNotification
// Posted when a device has been attached. Also posted for each device that is
// already attached when the Lookin_PTUSBHub starts listening.
//
// .userInfo = {
// DeviceID = 3;
// MessageType = Attached;
// Properties = {
// ConnectionSpeed = 480000000;
// ConnectionType = USB;
// DeviceID = 3;
// LocationID = 1234567890;
// ProductID = 1234;
// SerialNumber = 0123456789abcdef0123456789abcdef01234567;
// };
// }
//
FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidAttachNotification;
// Lookin_PTUSBDeviceDidDetachNotification
// Posted when a device has been detached.
//
// .userInfo = {
// DeviceID = 3;
// MessageType = Detached;
// }
//
FOUNDATION_EXPORT NSString * const Lookin_PTUSBDeviceDidDetachNotification;
// NSError domain
FOUNDATION_EXPORT NSString * const Lookin_PTUSBHubErrorDomain;
// Error codes returned with NSError.code for NSError domain Lookin_PTUSBHubErrorDomain
typedef enum {
PTUSBHubErrorBadDevice = 2,
PTUSBHubErrorConnectionRefused = 3,
} PTUSBHubError;
@interface Lookin_PTUSBHub : NSObject
// Shared, implicitly opened hub.
+ (Lookin_PTUSBHub*)sharedHub;
// Connect to a TCP *port* on a device, while the actual transport is over USB.
// Upon success, *error* is nil and *channel* is a duplex I/O channel.
// You can retrieve the underlying file descriptor using
// dispatch_io_get_descriptor(channel). The dispatch_io_t channel behaves just
// like any stream type dispatch_io_t, making it possible to use the same logic
// for both USB bridged connections and e.g. ethernet-based connections.
//
// *onStart* is called either when a connection failed, in which case the error
// argument is non-nil, or when the connection was successfully established (the
// error argument is nil). Must not be NULL.
//
// *onEnd* is called when a connection was open and just did close. If the error
// argument is non-nil, the channel closed because of an error. Pass NULL for no
// callback.
//
- (void)connectToDevice:(NSNumber*)deviceID
port:(int)port
onStart:(void(^)(NSError *error, dispatch_io_t channel))onStart
onEnd:(void(^)(NSError *error))onEnd;
// Start listening for devices. You only need to invoke this method on custom
// instances to start receiving notifications. The shared instance returned from
// +sharedHub is always in listening mode.
//
// *onStart* is called either when the system failed to start listening, in
// which case the error argument is non-nil, or when the receiver is listening.
// Pass NULL for no callback.
//
// *onEnd* is called when listening stopped. If the error argument is non-nil,
// listening stopped because of an error. Pass NULL for no callback.
//
- (void)listenOnQueue:(dispatch_queue_t)queue
onStart:(void(^)(NSError*))onStart
onEnd:(void(^)(NSError*))onEnd;
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,674 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
#import "Lookin_PTUSBHub.h"
#import "Lookin_PTPrivate.h"
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/ioctl.h>
#include <sys/un.h>
#include <err.h>
NSString * const Lookin_PTUSBHubErrorDomain = @"PTUSBHubError";
typedef uint32_t USBMuxPacketType;
enum {
USBMuxPacketTypeResult = 1,
USBMuxPacketTypeConnect = 2,
USBMuxPacketTypeListen = 3,
USBMuxPacketTypeDeviceAdd = 4,
USBMuxPacketTypeDeviceRemove = 5,
// ? = 6,
// ? = 7,
USBMuxPacketTypePlistPayload = 8,
};
typedef uint32_t USBMuxPacketProtocol;
enum {
USBMuxPacketProtocolBinary = 0,
USBMuxPacketProtocolPlist = 1,
};
typedef uint32_t USBMuxReplyCode;
enum {
USBMuxReplyCodeOK = 0,
USBMuxReplyCodeBadCommand = 1,
USBMuxReplyCodeBadDevice = 2,
USBMuxReplyCodeConnectionRefused = 3,
// ? = 4,
// ? = 5,
USBMuxReplyCodeBadVersion = 6,
};
typedef struct usbmux_packet {
uint32_t size;
USBMuxPacketProtocol protocol;
USBMuxPacketType type;
uint32_t tag;
char data[0];
} __attribute__((__packed__)) usbmux_packet_t;
static const uint32_t kUsbmuxPacketMaxPayloadSize = UINT32_MAX - (uint32_t)sizeof(usbmux_packet_t);
static uint32_t usbmux_packet_payload_size(usbmux_packet_t *upacket) {
return upacket->size - sizeof(usbmux_packet_t);
}
static void *usbmux_packet_payload(usbmux_packet_t *upacket) {
return (void*)upacket->data;
}
static void usbmux_packet_set_payload(usbmux_packet_t *upacket,
const void *payload,
uint32_t payloadLength)
{
memcpy(usbmux_packet_payload(upacket), payload, payloadLength);
}
static usbmux_packet_t *usbmux_packet_alloc(uint32_t payloadSize) {
assert(payloadSize <= kUsbmuxPacketMaxPayloadSize);
uint32_t upacketSize = sizeof(usbmux_packet_t) + payloadSize;
usbmux_packet_t *upacket = CFAllocatorAllocate(kCFAllocatorDefault, upacketSize, 0);
memset(upacket, 0, sizeof(usbmux_packet_t));
upacket->size = upacketSize;
return upacket;
}
static usbmux_packet_t *usbmux_packet_create(USBMuxPacketProtocol protocol,
USBMuxPacketType type,
uint32_t tag,
const void *payload,
uint32_t payloadSize)
{
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadSize);
if (!upacket) {
return NULL;
}
upacket->protocol = protocol;
upacket->type = type;
upacket->tag = tag;
if (payload && payloadSize) {
usbmux_packet_set_payload(upacket, payload, (uint32_t)payloadSize);
}
return upacket;
}
static void usbmux_packet_free(usbmux_packet_t *upacket) {
CFAllocatorDeallocate(kCFAllocatorDefault, upacket);
}
NSString * const Lookin_PTUSBDeviceDidAttachNotification = @"Lookin_PTUSBDeviceDidAttachNotification";
NSString * const Lookin_PTUSBDeviceDidDetachNotification = @"Lookin_PTUSBDeviceDidDetachNotification";
static NSString *kPlistPacketTypeListen = @"Listen";
static NSString *kPlistPacketTypeConnect = @"Connect";
// Represents a channel of communication between the host process and a remote
// (device) system. In practice, a Lookin_PTUSBChannel is connected to a usbmuxd
// endpoint which is configured to either listen for device changes (the
// PTUSBHub's channel is usually configured as a device notification listener) or
// configured as a TCP bridge (e.g. channels returned from PTUSBHub's
// connectToDevice:port:callback:). You should not create channels yourself, but
// let Lookin_PTUSBHub provide you with already configured channels.
@interface Lookin_PTUSBChannel : NSObject {
dispatch_io_t channel_;
dispatch_queue_t queue_;
uint32_t nextPacketTag_;
NSMutableDictionary *responseQueue_;
BOOL autoReadPackets_;
BOOL isReadingPackets_;
}
// The underlying dispatch I/O channel. This is handy if you want to handle your
// own I/O logic without Lookin_PTUSBChannel. Remember to dispatch_retain() the channel
// if you plan on using it as it might be released from the Lookin_PTUSBChannel at any
// point in time.
@property (readonly) dispatch_io_t dispatchChannel;
// The underlying file descriptor.
@property (readonly) dispatch_fd_t fileDescriptor;
// Send data
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback;
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback;
// Read data
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback;
// Close the channel, preventing further reads and writes, but letting currently
// queued reads and writes finish.
- (void)cancel;
// Close the channel, preventing further reads and writes, immediately
// terminating any ongoing reads and writes.
- (void)stop;
@end
@interface Lookin_PTUSBChannel (Private)
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload;
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError *error))onEnd;
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback;
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error;
- (uint32_t)nextPacketTag;
- (void)sendPacketOfType:(USBMuxPacketType)type overProtocol:(USBMuxPacketProtocol)protocol tag:(uint32_t)tag payload:(NSData*)payload callback:(void(^)(NSError*))callback;
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError *error))callback;
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError *error, NSDictionary *responsePacket))callback;
- (void)scheduleReadPacketWithCallback:(void(^)(NSError *error, NSDictionary *packet, uint32_t packetTag))callback;
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler;
- (void)setNeedsReadingPacket;
@end
@interface Lookin_PTUSBHub () {
Lookin_PTUSBChannel *channel_;
}
- (void)handleBroadcastPacket:(NSDictionary*)packet;
@end
@implementation Lookin_PTUSBHub
+ (Lookin_PTUSBHub*)sharedHub {
static Lookin_PTUSBHub *gSharedHub;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gSharedHub = [Lookin_PTUSBHub new];
[gSharedHub listenOnQueue:dispatch_get_main_queue() onStart:^(NSError *error) {
if (error) {
NSLog(@"Lookin_PTUSBHub failed to initialize: %@", error);
}
} onEnd:nil];
});
return gSharedHub;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)listenOnQueue:(dispatch_queue_t)queue onStart:(void(^)(NSError*))onStart onEnd:(void(^)(NSError*))onEnd {
if (channel_) {
if (onStart) onStart(nil);
return;
}
channel_ = [Lookin_PTUSBChannel new];
NSError *error = nil;
if ([channel_ openOnQueue:queue error:&error onEnd:onEnd]) {
[channel_ listenWithBroadcastHandler:^(NSDictionary *packet) { [self handleBroadcastPacket:packet]; } callback:onStart];
} else if (onStart) {
onStart(error);
}
}
- (void)connectToDevice:(NSNumber*)deviceID port:(int)port onStart:(void(^)(NSError*, dispatch_io_t))onStart onEnd:(void(^)(NSError*))onEnd {
Lookin_PTUSBChannel *channel = [Lookin_PTUSBChannel new];
NSError *error = nil;
if (![channel openOnQueue:dispatch_get_main_queue() error:&error onEnd:onEnd]) {
onStart(error, nil);
return;
}
port = ((port<<8) & 0xFF00) | (port>>8); // limit
NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeConnect
payload:[NSDictionary dictionaryWithObjectsAndKeys:
deviceID, @"DeviceID",
[NSNumber numberWithInt:port], @"PortNumber",
nil]];
[channel sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
NSError *error = error_;
[channel errorFromPlistResponse:responsePacket error:&error];
onStart(error, (error ? nil : channel.dispatchChannel) );
}];
}
- (void)handleBroadcastPacket:(NSDictionary*)packet {
NSString *messageType = [packet objectForKey:@"MessageType"];
if ([@"Attached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidAttachNotification object:self userInfo:packet];
} else if ([@"Detached" isEqualToString:messageType]) {
[[NSNotificationCenter defaultCenter] postNotificationName:Lookin_PTUSBDeviceDidDetachNotification object:self userInfo:packet];
} else {
NSLog(@"Warning: Unhandled broadcast message: %@", packet);
}
}
@end
#pragma mark -
@implementation Lookin_PTUSBChannel
+ (NSDictionary*)packetDictionaryWithPacketType:(NSString*)messageType payload:(NSDictionary*)payload {
NSDictionary *packet = nil;
static NSString *bundleName = nil;
static NSString *bundleVersion = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSDictionary *infoDict = [NSBundle mainBundle].infoDictionary;
if (infoDict) {
bundleName = [infoDict objectForKey:@"CFBundleName"];
bundleVersion = [[infoDict objectForKey:@"CFBundleVersion"] description];
}
});
if (bundleName) {
packet = [NSDictionary dictionaryWithObjectsAndKeys:
messageType, @"MessageType",
bundleName, @"ProgName",
bundleVersion, @"ClientVersionString",
nil];
} else {
packet = [NSDictionary dictionaryWithObjectsAndKeys:messageType, @"MessageType", nil];
}
if (payload) {
NSMutableDictionary *mpacket = [NSMutableDictionary dictionaryWithDictionary:payload];
[mpacket addEntriesFromDictionary:packet];
packet = mpacket;
}
return packet;
}
- (id)init {
if (!(self = [super init])) return nil;
return self;
}
- (void)dealloc {
//NSLog(@"dealloc %@", self);
if (channel_) {
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(channel_);
#endif
channel_ = nil;
}
}
- (BOOL)valid {
return !!channel_;
}
- (dispatch_io_t)dispatchChannel {
return channel_;
}
- (dispatch_fd_t)fileDescriptor {
return dispatch_io_get_descriptor(channel_);
}
- (BOOL)openOnQueue:(dispatch_queue_t)queue error:(NSError**)error onEnd:(void(^)(NSError*))onEnd {
assert(queue != nil);
assert(channel_ == nil);
queue_ = queue;
// Create socket
dispatch_fd_t fd = socket(AF_UNIX, SOCK_STREAM, 0);
if (fd == -1) {
if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
return NO;
}
// prevent SIGPIPE
int on = 1;
setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &on, sizeof(on));
// Connect socket
struct sockaddr_un addr;
addr.sun_family = AF_UNIX;
strcpy(addr.sun_path, "/var/run/usbmuxd");
socklen_t socklen = sizeof(addr);
if (connect(fd, (struct sockaddr*)&addr, socklen) == -1) {
if (error) *error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:errno userInfo:nil];
return NO;
}
channel_ = dispatch_io_create(DISPATCH_IO_STREAM, fd, queue_, ^(int error) {
close(fd);
if (onEnd) {
onEnd(error == 0 ? nil : [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil]);
}
});
return YES;
}
- (void)listenWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler callback:(void(^)(NSError*))callback {
autoReadPackets_ = YES;
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
NSDictionary *packet = [Lookin_PTUSBChannel packetDictionaryWithPacketType:kPlistPacketTypeListen payload:nil];
[self sendRequest:packet callback:^(NSError *error_, NSDictionary *responsePacket) {
if (!callback)
return;
NSError *error = error_;
[self errorFromPlistResponse:responsePacket error:&error];
callback(error);
}];
}
- (BOOL)errorFromPlistResponse:(NSDictionary*)packet error:(NSError**)error {
if (!*error) {
NSNumber *n = [packet objectForKey:@"Number"];
if (!n) {
*error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:(n ? n.integerValue : 0) userInfo:nil];
return NO;
}
USBMuxReplyCode replyCode = (USBMuxReplyCode)n.integerValue;
if (replyCode != 0) {
NSString *errmessage = @"Unspecified error";
switch (replyCode) {
case USBMuxReplyCodeBadCommand: errmessage = @"illegal command"; break;
case USBMuxReplyCodeBadDevice: errmessage = @"unknown device"; break;
case USBMuxReplyCodeConnectionRefused: errmessage = @"connection refused"; break;
case USBMuxReplyCodeBadVersion: errmessage = @"invalid version"; break;
default: break;
}
*error = [NSError errorWithDomain:Lookin_PTUSBHubErrorDomain code:replyCode userInfo:[NSDictionary dictionaryWithObject:errmessage forKey:NSLocalizedDescriptionKey]];
return NO;
}
}
return YES;
}
- (uint32_t)nextPacketTag {
return ++nextPacketTag_;
}
- (void)sendRequest:(NSDictionary*)packet callback:(void(^)(NSError*, NSDictionary*))callback {
uint32_t tag = [self nextPacketTag];
[self sendPacket:packet tag:tag callback:^(NSError *error) {
if (error) {
callback(error, nil);
return;
}
// TODO: timeout un-triggered callbacks in responseQueue_
if (!self->responseQueue_) self->responseQueue_ = [NSMutableDictionary new];
[self->responseQueue_ setObject:callback forKey:[NSNumber numberWithUnsignedInt:tag]];
}];
// We are awaiting a response
[self setNeedsReadingPacket];
}
- (void)setNeedsReadingPacket {
if (!isReadingPackets_) {
[self scheduleReadPacketWithBroadcastHandler:nil];
}
}
- (void)scheduleReadPacketWithBroadcastHandler:(void(^)(NSDictionary *packet))broadcastHandler {
assert(isReadingPackets_ == NO);
[self scheduleReadPacketWithCallback:^(NSError *error, NSDictionary *packet, uint32_t packetTag) {
// Interpret the package we just received
if (packetTag == 0) {
// Broadcast message
//NSLog(@"Received broadcast: %@", packet);
if (broadcastHandler) broadcastHandler(packet);
} else if (self->responseQueue_) {
// Reply
NSNumber *key = [NSNumber numberWithUnsignedInt:packetTag];
void(^requestCallback)(NSError*,NSDictionary*) = [self->responseQueue_ objectForKey:key];
if (requestCallback) {
[self->responseQueue_ removeObjectForKey:key];
requestCallback(error, packet);
} else {
NSLog(@"Warning: Ignoring reply packet for which there is no registered callback. Packet => %@", packet);
}
}
// Schedule reading another incoming package
if (self->autoReadPackets_) {
[self scheduleReadPacketWithBroadcastHandler:broadcastHandler];
}
}];
}
- (void)scheduleReadPacketWithCallback:(void(^)(NSError*, NSDictionary*, uint32_t))callback {
static usbmux_packet_t ref_upacket;
isReadingPackets_ = YES;
// Read the first `sizeof(ref_upacket.size)` bytes off the channel_
dispatch_io_read(channel_, 0, sizeof(ref_upacket.size), queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read 0,4: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (error) {
self->isReadingPackets_ = NO;
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
return;
}
// Read size of incoming usbmux_packet_t
uint32_t upacket_len = 0;
char *buffer = NULL;
size_t buffer_size = 0;
PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size); // objc_precise_lifetime guarantees 'map_data' isn't released before memcpy has a chance to do its thing
assert(buffer_size == sizeof(ref_upacket.size));
assert(sizeof(upacket_len) == sizeof(ref_upacket.size));
memcpy((void *)&(upacket_len), (const void *)buffer, buffer_size);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(map_data);
#endif
// Allocate a new usbmux_packet_t for the expected size
uint32_t payloadLength = upacket_len - (uint32_t)sizeof(usbmux_packet_t);
usbmux_packet_t *upacket = usbmux_packet_alloc(payloadLength);
// Read rest of the incoming usbmux_packet_t
off_t offset = sizeof(ref_upacket.size);
dispatch_io_read(self->channel_, offset, (size_t)(upacket->size - offset), self->queue_, ^(bool done, dispatch_data_t data, int error) {
//NSLog(@"dispatch_io_read X,Y: done=%d data=%p error=%d", done, data, error);
if (!done) {
return;
}
self->isReadingPackets_ = NO;
if (error) {
callback([[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:error userInfo:nil], nil, 0);
usbmux_packet_free(upacket);
return;
}
if (upacket_len > kUsbmuxPacketMaxPayloadSize) {
callback(
[[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:1 userInfo:@{
NSLocalizedDescriptionKey:@"Received a packet that is too large"}],
nil,
0
);
usbmux_packet_free(upacket);
return;
}
// Copy read bytes onto our usbmux_packet_t
char *buffer = NULL;
size_t buffer_size = 0;
PT_PRECISE_LIFETIME_UNUSED dispatch_data_t map_data = dispatch_data_create_map(data, (const void **)&buffer, &buffer_size);
assert(buffer_size == upacket->size - offset);
memcpy(((void *)(upacket))+offset, (const void *)buffer, buffer_size);
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(map_data);
#endif
// We only support plist protocol
if (upacket->protocol != USBMuxPacketProtocolPlist) {
callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package protocol" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Only one type of packet in the plist protocol
if (upacket->type != USBMuxPacketTypePlistPayload) {
callback([[NSError alloc] initWithDomain:Lookin_PTUSBHubErrorDomain code:0 userInfo:[NSDictionary dictionaryWithObject:@"Unexpected package type" forKey:NSLocalizedDescriptionKey]], nil, upacket->tag);
usbmux_packet_free(upacket);
return;
}
// Try to decode any payload as plist
NSError *err = nil;
NSDictionary *dict = nil;
if (usbmux_packet_payload_size(upacket)) {
dict = [NSPropertyListSerialization propertyListWithData:[NSData dataWithBytesNoCopy:usbmux_packet_payload(upacket) length:usbmux_packet_payload_size(upacket) freeWhenDone:NO] options:NSPropertyListImmutable format:NULL error:&err];
}
// Invoke callback
callback(err, dict, upacket->tag);
usbmux_packet_free(upacket);
});
});
}
- (void)sendPacketOfType:(USBMuxPacketType)type
overProtocol:(USBMuxPacketProtocol)protocol
tag:(uint32_t)tag
payload:(NSData*)payload
callback:(void(^)(NSError*))callback
{
assert(payload.length <= kUsbmuxPacketMaxPayloadSize);
usbmux_packet_t *upacket = usbmux_packet_create(
protocol,
type,
tag,
payload ? payload.bytes : nil,
(uint32_t)(payload ? payload.length : 0)
);
dispatch_data_t data = dispatch_data_create((const void*)upacket, upacket->size, queue_, ^{
// Free packet when data is freed
usbmux_packet_free(upacket);
});
//NSData *data1 = [NSData dataWithBytesNoCopy:(void*)upacket length:upacket->size freeWhenDone:NO];
//[data1 writeToFile:[NSString stringWithFormat:@"/Users/rsms/c-packet-%u.data", tag] atomically:NO];
[self sendDispatchData:data callback:callback];
}
- (void)sendPacket:(NSDictionary*)packet tag:(uint32_t)tag callback:(void(^)(NSError*))callback {
NSError *error = nil;
// NSPropertyListBinaryFormat_v1_0
NSData *plistData = [NSPropertyListSerialization dataWithPropertyList:packet format:NSPropertyListXMLFormat_v1_0 options:0 error:&error];
if (!plistData) {
callback(error);
} else {
[self sendPacketOfType:USBMuxPacketTypePlistPayload overProtocol:USBMuxPacketProtocolPlist tag:tag payload:plistData callback:callback];
}
}
- (void)sendDispatchData:(dispatch_data_t)data callback:(void(^)(NSError*))callback {
off_t offset = 0;
dispatch_io_write(channel_, offset, data, queue_, ^(bool done, dispatch_data_t data, int _errno) {
//NSLog(@"dispatch_io_write: done=%d data=%p error=%d", done, data, error);
if (!done)
return;
if (callback) {
NSError *err = nil;
if (_errno) err = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
callback(err);
}
});
#if PT_DISPATCH_RETAIN_RELEASE
dispatch_release(data); // Release our ref. A ref is still held by dispatch_io_write
#endif
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-getter-return-value"
- (void)sendData:(NSData*)data callback:(void(^)(NSError*))callback {
dispatch_data_t ddata = dispatch_data_create((const void*)data.bytes, data.length, queue_, ^{
// trick to have the block capture and retain the data
[data length];
});
[self sendDispatchData:ddata callback:callback];
}
#pragma clang diagnostic pop
- (void)readFromOffset:(off_t)offset length:(size_t)length callback:(void(^)(NSError *error, dispatch_data_t data))callback {
dispatch_io_read(channel_, offset, length, queue_, ^(bool done, dispatch_data_t data, int _errno) {
if (!done)
return;
NSError *error = nil;
if (_errno != 0) {
error = [[NSError alloc] initWithDomain:NSPOSIXErrorDomain code:_errno userInfo:nil];
}
callback(error, data);
});
}
- (void)cancel {
if (channel_) {
dispatch_io_close(channel_, 0);
}
}
- (void)stop {
if (channel_) {
dispatch_io_close(channel_, DISPATCH_IO_STOP);
}
}
@end
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */

View File

@@ -0,0 +1,28 @@
#ifdef SHOULD_COMPILE_LOOKIN_SERVER
//
// Peertalk.h
// Peertalk
//
// Created by Marek Cirkos on 12/04/2016.
//
//
#import <Foundation/Foundation.h>
//! Project version number for Peertalk.
FOUNDATION_EXPORT double PeertalkVersionNumber;
//! Project version string for Peertalk.
FOUNDATION_EXPORT const unsigned char PeertalkVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import <Peertalk/PublicHeader.h>
#import "Lookin_PTChannel.h"
#import "Lookin_PTProtocol.h"
#import "Lookin_PTUSBHub.h"
#endif /* SHOULD_COMPILE_LOOKIN_SERVER */