/iKopter/trunk/Classes/Communication/AsyncSocket.h |
---|
0,0 → 1,511 |
// |
// AsyncSocket.h |
// |
// This class is in the public domain. |
// Originally created by Dustin Voss on Wed Jan 29 2003. |
// Updated and maintained by Deusty Designs and the Mac development community. |
// |
// http://code.google.com/p/cocoaasyncsocket/ |
// |
#import <Foundation/Foundation.h> |
@class AsyncSocket; |
@class AsyncReadPacket; |
@class AsyncWritePacket; |
extern NSString *const AsyncSocketException; |
extern NSString *const AsyncSocketErrorDomain; |
enum AsyncSocketError |
{ |
AsyncSocketCFSocketError = kCFSocketError, // From CFSocketError enum. |
AsyncSocketNoError = 0, // Never used. |
AsyncSocketCanceledError, // onSocketWillConnect: returned NO. |
AsyncSocketConnectTimeoutError, |
AsyncSocketReadMaxedOutError, // Reached set maxLength without completing |
AsyncSocketReadTimeoutError, |
AsyncSocketWriteTimeoutError |
}; |
typedef enum AsyncSocketError AsyncSocketError; |
@interface NSObject (AsyncSocketDelegate) |
/** |
* In the event of an error, the socket is closed. |
* You may call "unreadData" during this call-back to get the last bit of data off the socket. |
* When connecting, this delegate method may be called |
* before"onSocket:didAcceptNewSocket:" or "onSocket:didConnectToHost:". |
**/ |
- (void)onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; |
/** |
* Called when a socket disconnects with or without error. If you want to release a socket after it disconnects, |
* do so here. It is not safe to do that during "onSocket:willDisconnectWithError:". |
* |
* If you call the disconnect method, and the socket wasn't already disconnected, |
* this delegate method will be called before the disconnect method returns. |
**/ |
- (void)onSocketDidDisconnect:(AsyncSocket *)sock; |
/** |
* Called when a socket accepts a connection. Another socket is spawned to handle it. The new socket will have |
* the same delegate and will call "onSocket:didConnectToHost:port:". |
**/ |
- (void)onSocket:(AsyncSocket *)sock didAcceptNewSocket:(AsyncSocket *)newSocket; |
/** |
* Called when a new socket is spawned to handle a connection. This method should return the run-loop of the |
* thread on which the new socket and its delegate should operate. If omitted, [NSRunLoop currentRunLoop] is used. |
**/ |
- (NSRunLoop *)onSocket:(AsyncSocket *)sock wantsRunLoopForNewSocket:(AsyncSocket *)newSocket; |
/** |
* Called when a socket is about to connect. This method should return YES to continue, or NO to abort. |
* If aborted, will result in AsyncSocketCanceledError. |
* |
* If the connectToHost:onPort:error: method was called, the delegate will be able to access and configure the |
* CFReadStream and CFWriteStream as desired prior to connection. |
* |
* If the connectToAddress:error: method was called, the delegate will be able to access and configure the |
* CFSocket and CFSocketNativeHandle (BSD socket) as desired prior to connection. You will be able to access and |
* configure the CFReadStream and CFWriteStream in the onSocket:didConnectToHost:port: method. |
**/ |
- (BOOL)onSocketWillConnect:(AsyncSocket *)sock; |
/** |
* Called when a socket connects and is ready for reading and writing. |
* The host parameter will be an IP address, not a DNS name. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; |
/** |
* Called when a socket has completed reading the requested data into memory. |
* Not called if there is an error. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; |
/** |
* Called when a socket has read in data, but has not yet completed the read. |
* This would occur if using readToData: or readToLength: methods. |
* It may be used to for things such as updating progress bars. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didReadPartialDataOfLength:(CFIndex)partialLength tag:(long)tag; |
/** |
* Called when a socket has completed writing the requested data. Not called if there is an error. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; |
/** |
* Called when a socket has written some data, but has not yet completed the entire write. |
* It may be used to for things such as updating progress bars. |
**/ |
- (void)onSocket:(AsyncSocket *)sock didWritePartialDataOfLength:(CFIndex)partialLength tag:(long)tag; |
/** |
* Called if a read operation has reached its timeout without completing. |
* This method allows you to optionally extend the timeout. |
* If you return a positive time interval (> 0) the read's timeout will be extended by the given amount. |
* If you don't implement this method, or return a non-positive time interval (<= 0) the read will timeout as usual. |
* |
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. |
* The length parameter is the number of bytes that have been read so far for the read operation. |
* |
* Note that this method may be called multiple times for a single read if you return positive numbers. |
**/ |
- (NSTimeInterval)onSocket:(AsyncSocket *)sock |
shouldTimeoutReadWithTag:(long)tag |
elapsed:(NSTimeInterval)elapsed |
bytesDone:(CFIndex)length; |
/** |
* Called if a write operation has reached its timeout without completing. |
* This method allows you to optionally extend the timeout. |
* If you return a positive time interval (> 0) the write's timeout will be extended by the given amount. |
* If you don't implement this method, or return a non-positive time interval (<= 0) the write will timeout as usual. |
* |
* The elapsed parameter is the sum of the original timeout, plus any additions previously added via this method. |
* The length parameter is the number of bytes that have been written so far for the write operation. |
* |
* Note that this method may be called multiple times for a single write if you return positive numbers. |
**/ |
- (NSTimeInterval)onSocket:(AsyncSocket *)sock |
shouldTimeoutWriteWithTag:(long)tag |
elapsed:(NSTimeInterval)elapsed |
bytesDone:(CFIndex)length; |
/** |
* Called after the socket has successfully completed SSL/TLS negotiation. |
* This method is not called unless you use the provided startTLS method. |
* |
* If a SSL/TLS negotiation fails (invalid certificate, etc) then the socket will immediately close, |
* and the onSocket:willDisconnectWithError: delegate method will be called with the specific SSL error code. |
**/ |
- (void)onSocketDidSecure:(AsyncSocket *)sock; |
@end |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
@interface AsyncSocket : NSObject |
{ |
CFSocketRef theSocket4; // IPv4 accept or connect socket |
CFSocketRef theSocket6; // IPv6 accept or connect socket |
CFReadStreamRef theReadStream; |
CFWriteStreamRef theWriteStream; |
CFRunLoopSourceRef theSource4; // For theSocket4 |
CFRunLoopSourceRef theSource6; // For theSocket6 |
CFRunLoopRef theRunLoop; |
CFSocketContext theContext; |
NSArray *theRunLoopModes; |
NSTimer *theConnectTimer; |
NSMutableArray *theReadQueue; |
AsyncReadPacket *theCurrentRead; |
NSTimer *theReadTimer; |
NSMutableData *partialReadBuffer; |
NSMutableArray *theWriteQueue; |
AsyncWritePacket *theCurrentWrite; |
NSTimer *theWriteTimer; |
id theDelegate; |
UInt16 theFlags; |
long theUserData; |
} |
- (id)init; |
- (id)initWithDelegate:(id)delegate; |
- (id)initWithDelegate:(id)delegate userData:(long)userData; |
/* String representation is long but has no "\n". */ |
- (NSString *)description; |
/** |
* Use "canSafelySetDelegate" to see if there is any pending business (reads and writes) with the current delegate |
* before changing it. It is, of course, safe to change the delegate before connecting or accepting connections. |
**/ |
- (id)delegate; |
- (BOOL)canSafelySetDelegate; |
- (void)setDelegate:(id)delegate; |
/* User data can be a long, or an id or void * cast to a long. */ |
- (long)userData; |
- (void)setUserData:(long)userData; |
/* Don't use these to read or write. And don't close them either! */ |
- (CFSocketRef)getCFSocket; |
- (CFReadStreamRef)getCFReadStream; |
- (CFWriteStreamRef)getCFWriteStream; |
// Once one of the accept or connect methods are called, the AsyncSocket instance is locked in |
// and the other accept/connect methods can't be called without disconnecting the socket first. |
// If the attempt fails or times out, these methods either return NO or |
// call "onSocket:willDisconnectWithError:" and "onSockedDidDisconnect:". |
// When an incoming connection is accepted, AsyncSocket invokes several delegate methods. |
// These methods are (in chronological order): |
// 1. onSocket:didAcceptNewSocket: |
// 2. onSocket:wantsRunLoopForNewSocket: |
// 3. onSocketWillConnect: |
// |
// Your server code will need to retain the accepted socket (if you want to accept it). |
// The best place to do this is probably in the onSocket:didAcceptNewSocket: method. |
// |
// After the read and write streams have been setup for the newly accepted socket, |
// the onSocket:didConnectToHost:port: method will be called on the proper run loop. |
// |
// Multithreading Note: If you're going to be moving the newly accepted socket to another run |
// loop by implementing onSocket:wantsRunLoopForNewSocket:, then you should wait until the |
// onSocket:didConnectToHost:port: method before calling read, write, or startTLS methods. |
// Otherwise read/write events are scheduled on the incorrect runloop, and chaos may ensue. |
/** |
* Tells the socket to begin listening and accepting connections on the given port. |
* When a connection comes in, the AsyncSocket instance will call the various delegate methods (see above). |
* The socket will listen on all available interfaces (e.g. wifi, ethernet, etc) |
**/ |
- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr; |
/** |
* This method is the same as acceptOnPort:error: with the additional option |
* of specifying which interface to listen on. So, for example, if you were writing code for a server that |
* has multiple IP addresses, you could specify which address you wanted to listen on. Or you could use it |
* to specify that the socket should only accept connections over ethernet, and not other interfaces such as wifi. |
* You may also use the special strings "localhost" or "loopback" to specify that |
* the socket only accept connections from the local machine. |
* |
* To accept connections on any interface pass nil, or simply use the acceptOnPort:error: method. |
**/ |
- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr; |
/** |
* Connects to the given host and port. |
* The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2") |
**/ |
- (BOOL)connectToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; |
/** |
* This method is the same as connectToHost:onPort:error: with an additional timeout option. |
* To not time out use a negative time interval, or simply use the connectToHost:onPort:error: method. |
**/ |
- (BOOL)connectToHost:(NSString *)hostname |
onPort:(UInt16)port |
withTimeout:(NSTimeInterval)timeout |
error:(NSError **)errPtr; |
/** |
* Connects to the given address, specified as a sockaddr structure wrapped in a NSData object. |
* For example, a NSData object returned from NSNetservice's addresses method. |
* |
* If you have an existing struct sockaddr you can convert it to a NSData object like so: |
* struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; |
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; |
**/ |
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
/** |
* This method is the same as connectToAddress:error: with an additional timeout option. |
* To not time out use a negative time interval, or simply use the connectToAddress:error: method. |
**/ |
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr; |
/** |
* Disconnects immediately. Any pending reads or writes are dropped. |
* If the socket is not already disconnected, the onSocketDidDisconnect delegate method |
* will be called immediately, before this method returns. |
* |
* Please note the recommended way of releasing an AsyncSocket instance (e.g. in a dealloc method) |
* [asyncSocket setDelegate:nil]; |
* [asyncSocket disconnect]; |
* [asyncSocket release]; |
**/ |
- (void)disconnect; |
/** |
* Disconnects after all pending reads have completed. |
* After calling this, the read and write methods will do nothing. |
* The socket will disconnect even if there are still pending writes. |
**/ |
- (void)disconnectAfterReading; |
/** |
* Disconnects after all pending writes have completed. |
* After calling this, the read and write methods will do nothing. |
* The socket will disconnect even if there are still pending reads. |
**/ |
- (void)disconnectAfterWriting; |
/** |
* Disconnects after all pending reads and writes have completed. |
* After calling this, the read and write methods will do nothing. |
**/ |
- (void)disconnectAfterReadingAndWriting; |
/* Returns YES if the socket and streams are open, connected, and ready for reading and writing. */ |
- (BOOL)isConnected; |
/** |
* Returns the local or remote host and port to which this socket is connected, or nil and 0 if not connected. |
* The host will be an IP address. |
**/ |
- (NSString *)connectedHost; |
- (UInt16)connectedPort; |
- (NSString *)localHost; |
- (UInt16)localPort; |
/** |
* Returns the local or remote address to which this socket is connected, |
* specified as a sockaddr structure wrapped in a NSData object. |
* |
* See also the connectedHost, connectedPort, localHost and localPort methods. |
**/ |
- (NSData *)connectedAddress; |
- (NSData *)localAddress; |
/** |
* Returns whether the socket is IPv4 or IPv6. |
* An accepting socket may be both. |
**/ |
- (BOOL)isIPv4; |
- (BOOL)isIPv6; |
// The readData and writeData methods won't block. |
// |
// You may optionally set a timeout for any read/write operation. (To not timeout, use a negative time interval.) |
// If a read/write opertion times out, the corresponding "onSocket:shouldTimeout..." delegate method |
// is called to optionally allow you to extend the timeout. |
// Upon a timeout, the "onSocket:willDisconnectWithError:" method is called, followed by "onSocketDidDisconnect". |
// |
// The tag is for your convenience. |
// You can use it as an array index, step number, state id, pointer, etc., just like the socket's user data. |
/** |
* This will read a certain number of bytes into memory, and call the delegate method when those bytes have been read. |
* |
* If the length is 0, this method does nothing and the delegate is not called. |
* If the timeout value is negative, the read operation will not use a timeout. |
**/ |
- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
/** |
* This reads bytes until (and including) the passed "data" parameter, which acts as a separator. |
* The bytes and the separator are returned by the delegate method. |
* |
* If you pass nil or zero-length data as the "data" parameter, |
* the method will do nothing, and the delegate will not be called. |
* If the timeout value is negative, the read operation will not use a timeout. |
* |
* To read a line from the socket, use the line separator (e.g. CRLF for HTTP, see below) as the "data" parameter. |
* Note that this method is not character-set aware, so if a separator can occur naturally as part of the encoding for |
* a character, the read will prematurely end. |
**/ |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
/** |
* Same as readDataToData:withTimeout:tag, with the additional restriction that the amount of data read |
* may not surpass the given maxLength (specified in bytes). |
* |
* If you pass a maxLength parameter that is less than the length of the data parameter, |
* the method will do nothing, and the delegate will not be called. |
* |
* If the max length is surpassed, it is treated the same as a timeout - the socket is closed. |
* |
* Pass -1 as maxLength if no length restriction is desired, or simply use the readDataToData:withTimeout:tag method. |
**/ |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag; |
/** |
* Reads the first available bytes that become available on the socket. |
* |
* If the timeout value is negative, the read operation will not use a timeout. |
**/ |
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag; |
/** |
* Writes data to the socket, and calls the delegate when finished. |
* |
* If you pass in nil or zero-length data, this method does nothing and the delegate will not be called. |
* If the timeout value is negative, the write operation will not use a timeout. |
**/ |
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; |
/** |
* Returns progress of current read or write, from 0.0 to 1.0, or NaN if no read/write (use isnan() to check). |
* "tag", "done" and "total" will be filled in if they aren't NULL. |
**/ |
- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; |
- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total; |
/** |
* Secures the connection using SSL/TLS. |
* |
* This method may be called at any time, and the TLS handshake will occur after all pending reads and writes |
* are finished. This allows one the option of sending a protocol dependent StartTLS message, and queuing |
* the upgrade to TLS at the same time, without having to wait for the write to finish. |
* Any reads or writes scheduled after this method is called will occur over the secured connection. |
* |
* The possible keys and values for the TLS settings are well documented. |
* Some possible keys are: |
* - kCFStreamSSLLevel |
* - kCFStreamSSLAllowsExpiredCertificates |
* - kCFStreamSSLAllowsExpiredRoots |
* - kCFStreamSSLAllowsAnyRoot |
* - kCFStreamSSLValidatesCertificateChain |
* - kCFStreamSSLPeerName |
* - kCFStreamSSLCertificates |
* - kCFStreamSSLIsServer |
* |
* Please refer to Apple's documentation for associated values, as well as other possible keys. |
* |
* If you pass in nil or an empty dictionary, the default settings will be used. |
* |
* The default settings will check to make sure the remote party's certificate is signed by a |
* trusted 3rd party certificate agency (e.g. verisign) and that the certificate is not expired. |
* However it will not verify the name on the certificate unless you |
* give it a name to verify against via the kCFStreamSSLPeerName key. |
* The security implications of this are important to understand. |
* Imagine you are attempting to create a secure connection to MySecureServer.com, |
* but your socket gets directed to MaliciousServer.com because of a hacked DNS server. |
* If you simply use the default settings, and MaliciousServer.com has a valid certificate, |
* the default settings will not detect any problems since the certificate is valid. |
* To properly secure your connection in this particular scenario you |
* should set the kCFStreamSSLPeerName property to "MySecureServer.com". |
* If you do not know the peer name of the remote host in advance (for example, you're not sure |
* if it will be "domain.com" or "www.domain.com"), then you can use the default settings to validate the |
* certificate, and then use the X509Certificate class to verify the issuer after the socket has been secured. |
* The X509Certificate class is part of the CocoaAsyncSocket open source project. |
**/ |
- (void)startTLS:(NSDictionary *)tlsSettings; |
/** |
* For handling readDataToData requests, data is necessarily read from the socket in small increments. |
* The performance can be much improved by allowing AsyncSocket to read larger chunks at a time and |
* store any overflow in a small internal buffer. |
* This is termed pre-buffering, as some data may be read for you before you ask for it. |
* If you use readDataToData a lot, enabling pre-buffering will result in better performance, especially on the iPhone. |
* |
* The default pre-buffering state is controlled by the DEFAULT_PREBUFFERING definition. |
* It is highly recommended one leave this set to YES. |
* |
* This method exists in case pre-buffering needs to be disabled by default for some unforeseen reason. |
* In that case, this method exists to allow one to easily enable pre-buffering when ready. |
**/ |
- (void)enablePreBuffering; |
/** |
* When you create an AsyncSocket, it is added to the runloop of the current thread. |
* So for manually created sockets, it is easiest to simply create the socket on the thread you intend to use it. |
* |
* If a new socket is accepted, the delegate method onSocket:wantsRunLoopForNewSocket: is called to |
* allow you to place the socket on a separate thread. This works best in conjunction with a thread pool design. |
* |
* If, however, you need to move the socket to a separate thread at a later time, this |
* method may be used to accomplish the task. |
* |
* This method must be called from the thread/runloop the socket is currently running on. |
* |
* Note: After calling this method, all further method calls to this object should be done from the given runloop. |
* Also, all delegate calls will be sent on the given runloop. |
**/ |
- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop; |
/** |
* Allows you to configure which run loop modes the socket uses. |
* The default set of run loop modes is NSDefaultRunLoopMode. |
* |
* If you'd like your socket to continue operation during other modes, you may want to add modes such as |
* NSModalPanelRunLoopMode or NSEventTrackingRunLoopMode. Or you may simply want to use NSRunLoopCommonModes. |
* |
* Accepted sockets will automatically inherit the same run loop modes as the listening socket. |
* |
* Note: NSRunLoopCommonModes is defined in 10.5. For previous versions one can use kCFRunLoopCommonModes. |
**/ |
- (BOOL)setRunLoopModes:(NSArray *)runLoopModes; |
/** |
* Returns the current run loop modes the AsyncSocket instance is operating in. |
* The default set of run loop modes is NSDefaultRunLoopMode. |
**/ |
- (NSArray *)runLoopModes; |
/** |
* In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read |
* any data that's left on the socket. |
**/ |
- (NSData *)unreadData; |
/* A few common line separators, for use with the readDataToData:... methods. */ |
+ (NSData *)CRLFData; // 0x0D0A |
+ (NSData *)CRData; // 0x0D |
+ (NSData *)LFData; // 0x0A |
+ (NSData *)ZeroData; // 0x00 |
@end |
/iKopter/trunk/Classes/Communication/AsyncSocket.m |
---|
0,0 → 1,3184 |
// |
// AsyncSocket.m |
// |
// This class is in the public domain. |
// Originally created by Dustin Voss on Wed Jan 29 2003. |
// Updated and maintained by Deusty Designs and the Mac development community. |
// |
// http://code.google.com/p/cocoaasyncsocket/ |
// |
#import "AsyncSocket.h" |
#import <sys/socket.h> |
#import <netinet/in.h> |
#import <arpa/inet.h> |
#import <netdb.h> |
#if TARGET_OS_IPHONE |
// Note: You may need to add the CFNetwork Framework to your project |
#import <CFNetwork/CFNetwork.h> |
#endif |
#pragma mark Declarations |
#define DEFAULT_PREBUFFERING YES // Whether pre-buffering is enabled by default |
#define READQUEUE_CAPACITY 5 // Initial capacity |
#define WRITEQUEUE_CAPACITY 5 // Initial capacity |
#define READALL_CHUNKSIZE 256 // Incremental increase in buffer size |
#define WRITE_CHUNKSIZE (1024 * 4) // Limit on size of each write pass |
NSString *const AsyncSocketException = @"AsyncSocketException"; |
NSString *const AsyncSocketErrorDomain = @"AsyncSocketErrorDomain"; |
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
// Mutex lock used by all instances of AsyncSocket, to protect getaddrinfo. |
// Prior to Mac OS X 10.5 this method was not thread-safe. |
static NSString *getaddrinfoLock = @"lock"; |
#endif |
enum AsyncSocketFlags |
{ |
kEnablePreBuffering = 1 << 0, // If set, pre-buffering is enabled |
kDidPassConnectMethod = 1 << 1, // If set, disconnection results in delegate call |
kDidCompleteOpenForRead = 1 << 2, // If set, open callback has been called for read stream |
kDidCompleteOpenForWrite = 1 << 3, // If set, open callback has been called for write stream |
kStartingReadTLS = 1 << 4, // If set, we're waiting for TLS negotiation to complete |
kStartingWriteTLS = 1 << 5, // If set, we're waiting for TLS negotiation to complete |
kForbidReadsWrites = 1 << 6, // If set, no new reads or writes are allowed |
kDisconnectAfterReads = 1 << 7, // If set, disconnect after no more reads are queued |
kDisconnectAfterWrites = 1 << 8, // If set, disconnect after no more writes are queued |
kClosingWithError = 1 << 9, // If set, the socket is being closed due to an error |
kDequeueReadScheduled = 1 << 10, // If set, a maybeDequeueRead operation is already scheduled |
kDequeueWriteScheduled = 1 << 11, // If set, a maybeDequeueWrite operation is already scheduled |
kSocketCanAcceptBytes = 1 << 12, // If set, we know socket can accept bytes. If unset, it's unknown. |
kSocketHasBytesAvailable = 1 << 13, // If set, we know socket has bytes available. If unset, it's unknown. |
}; |
@interface AsyncSocket (Private) |
// Connecting |
- (void)startConnectTimeout:(NSTimeInterval)timeout; |
- (void)endConnectTimeout; |
// Socket Implementation |
- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr; |
- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; |
- (BOOL)configureSocketAndReturnError:(NSError **)errPtr; |
- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; |
- (void)doAcceptWithSocket:(CFSocketNativeHandle)newSocket; |
- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)err; |
// Stream Implementation |
- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr; |
- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr; |
- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr; |
- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr; |
- (BOOL)openStreamsAndReturnError:(NSError **)errPtr; |
- (void)doStreamOpen; |
- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr; |
// Disconnect Implementation |
- (void)closeWithError:(NSError *)err; |
- (void)recoverUnreadData; |
- (void)emptyQueues; |
- (void)close; |
// Errors |
- (NSError *)getErrnoError; |
- (NSError *)getAbortError; |
- (NSError *)getStreamError; |
- (NSError *)getSocketError; |
- (NSError *)getConnectTimeoutError; |
- (NSError *)getReadMaxedOutError; |
- (NSError *)getReadTimeoutError; |
- (NSError *)getWriteTimeoutError; |
- (NSError *)errorFromCFStreamError:(CFStreamError)err; |
// Diagnostics |
- (BOOL)isSocketConnected; |
- (BOOL)areStreamsConnected; |
- (NSString *)connectedHost:(CFSocketRef)socket; |
- (UInt16)connectedPort:(CFSocketRef)socket; |
- (NSString *)localHost:(CFSocketRef)socket; |
- (UInt16)localPort:(CFSocketRef)socket; |
- (NSString *)addressHost:(CFDataRef)cfaddr; |
- (UInt16)addressPort:(CFDataRef)cfaddr; |
// Reading |
- (void)doBytesAvailable; |
- (void)completeCurrentRead; |
- (void)endCurrentRead; |
- (void)scheduleDequeueRead; |
- (void)maybeDequeueRead; |
- (void)doReadTimeout:(NSTimer *)timer; |
// Writing |
- (void)doSendBytes; |
- (void)completeCurrentWrite; |
- (void)endCurrentWrite; |
- (void)scheduleDequeueWrite; |
- (void)maybeDequeueWrite; |
- (void)maybeScheduleDisconnect; |
- (void)doWriteTimeout:(NSTimer *)timer; |
// Run Loop |
- (void)runLoopAddSource:(CFRunLoopSourceRef)source; |
- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source; |
- (void)runLoopAddTimer:(NSTimer *)timer; |
- (void)runLoopRemoveTimer:(NSTimer *)timer; |
- (void)runLoopUnscheduleReadStream; |
- (void)runLoopUnscheduleWriteStream; |
// Security |
- (void)maybeStartTLS; |
- (void)onTLSHandshakeSuccessful; |
// Callbacks |
- (void)doCFCallback:(CFSocketCallBackType)type forSocket:(CFSocketRef)sock withAddress:(NSData *)address withData:(const void *)pData; |
- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream; |
- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream; |
@end |
static void MyCFSocketCallback(CFSocketRef, CFSocketCallBackType, CFDataRef, const void *, void *); |
static void MyCFReadStreamCallback(CFReadStreamRef stream, CFStreamEventType type, void *pInfo); |
static void MyCFWriteStreamCallback(CFWriteStreamRef stream, CFStreamEventType type, void *pInfo); |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* The AsyncReadPacket encompasses the instructions for any given read. |
* The content of a read packet allows the code to determine if we're: |
* - reading to a certain length |
* - reading to a certain separator |
* - or simply reading the first chunk of available data |
**/ |
@interface AsyncReadPacket : NSObject |
{ |
@public |
NSMutableData *buffer; |
CFIndex bytesDone; |
NSTimeInterval timeout; |
CFIndex maxLength; |
long tag; |
NSData *term; |
BOOL readAllAvailableData; |
} |
- (id)initWithData:(NSMutableData *)d |
timeout:(NSTimeInterval)t |
tag:(long)i |
readAllAvailable:(BOOL)a |
terminator:(NSData *)e |
maxLength:(CFIndex)m; |
- (unsigned)readLengthForTerm; |
- (unsigned)prebufferReadLengthForTerm; |
- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes; |
@end |
@implementation AsyncReadPacket |
- (id)initWithData:(NSMutableData *)d |
timeout:(NSTimeInterval)t |
tag:(long)i |
readAllAvailable:(BOOL)a |
terminator:(NSData *)e |
maxLength:(CFIndex)m |
{ |
if((self = [super init])) |
{ |
buffer = [d retain]; |
timeout = t; |
tag = i; |
readAllAvailableData = a; |
term = [e copy]; |
bytesDone = 0; |
maxLength = m; |
} |
return self; |
} |
/** |
* For read packets with a set terminator, returns the safe length of data that can be read |
* without going over a terminator, or the maxLength. |
* |
* It is assumed the terminator has not already been read. |
**/ |
- (unsigned)readLengthForTerm |
{ |
NSAssert(term != nil, @"Searching for term in data when there is no term."); |
// What we're going to do is look for a partial sequence of the terminator at the end of the buffer. |
// If a partial sequence occurs, then we must assume the next bytes to arrive will be the rest of the term, |
// and we can only read that amount. |
// Otherwise, we're safe to read the entire length of the term. |
unsigned result = [term length]; |
// Shortcut when term is a single byte |
if(result == 1) return result; |
// i = index within buffer at which to check data |
// j = length of term to check against |
// Note: Beware of implicit casting rules |
// This could give you -1: MAX(0, (0 - [term length] + 1)); |
CFIndex i = MAX(0, (CFIndex)(bytesDone - [term length] + 1)); |
CFIndex j = MIN([term length] - 1, bytesDone); |
while(i < bytesDone) |
{ |
const void *subBuffer = [buffer bytes] + i; |
if(memcmp(subBuffer, [term bytes], j) == 0) |
{ |
result = [term length] - j; |
break; |
} |
i++; |
j--; |
} |
if(maxLength > 0) |
return MIN(result, (maxLength - bytesDone)); |
else |
return result; |
} |
/** |
* Assuming pre-buffering is enabled, returns the amount of data that can be read |
* without going over the maxLength. |
**/ |
- (unsigned)prebufferReadLengthForTerm |
{ |
if(maxLength > 0) |
return MIN(READALL_CHUNKSIZE, (maxLength - bytesDone)); |
else |
return READALL_CHUNKSIZE; |
} |
/** |
* For read packets with a set terminator, scans the packet buffer for the term. |
* It is assumed the terminator had not been fully read prior to the new bytes. |
* |
* If the term is found, the number of excess bytes after the term are returned. |
* If the term is not found, this method will return -1. |
* |
* Note: A return value of zero means the term was found at the very end. |
**/ |
- (CFIndex)searchForTermAfterPreBuffering:(CFIndex)numBytes |
{ |
NSAssert(term != nil, @"Searching for term in data when there is no term."); |
// We try to start the search such that the first new byte read matches up with the last byte of the term. |
// We continue searching forward after this until the term no longer fits into the buffer. |
// Note: Beware of implicit casting rules |
// This could give you -1: MAX(0, 1 - 1 - [term length] + 1); |
CFIndex i = MAX(0, (CFIndex)(bytesDone - numBytes - [term length] + 1)); |
while(i + [term length] <= bytesDone) |
{ |
const void *subBuffer = [buffer bytes] + i; |
if(memcmp(subBuffer, [term bytes], [term length]) == 0) |
{ |
return bytesDone - (i + [term length]); |
} |
i++; |
} |
return -1; |
} |
- (void)dealloc |
{ |
[buffer release]; |
[term release]; |
[super dealloc]; |
} |
@end |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* The AsyncWritePacket encompasses the instructions for any given write. |
**/ |
@interface AsyncWritePacket : NSObject |
{ |
@public |
NSData *buffer; |
CFIndex bytesDone; |
long tag; |
NSTimeInterval timeout; |
} |
- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i; |
@end |
@implementation AsyncWritePacket |
- (id)initWithData:(NSData *)d timeout:(NSTimeInterval)t tag:(long)i |
{ |
if((self = [super init])) |
{ |
buffer = [d retain]; |
timeout = t; |
tag = i; |
bytesDone = 0; |
} |
return self; |
} |
- (void)dealloc |
{ |
[buffer release]; |
[super dealloc]; |
} |
@end |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* The AsyncSpecialPacket encompasses special instructions for interruptions in the read/write queues. |
* This class my be altered to support more than just TLS in the future. |
**/ |
@interface AsyncSpecialPacket : NSObject |
{ |
@public |
NSDictionary *tlsSettings; |
} |
- (id)initWithTLSSettings:(NSDictionary *)settings; |
@end |
@implementation AsyncSpecialPacket |
- (id)initWithTLSSettings:(NSDictionary *)settings |
{ |
if((self = [super init])) |
{ |
tlsSettings = [settings copy]; |
} |
return self; |
} |
- (void)dealloc |
{ |
[tlsSettings release]; |
[super dealloc]; |
} |
@end |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
@implementation AsyncSocket |
- (id)init |
{ |
return [self initWithDelegate:nil userData:0]; |
} |
- (id)initWithDelegate:(id)delegate |
{ |
return [self initWithDelegate:delegate userData:0]; |
} |
// Designated initializer. |
- (id)initWithDelegate:(id)delegate userData:(long)userData |
{ |
if((self = [super init])) |
{ |
theFlags = DEFAULT_PREBUFFERING ? kEnablePreBuffering : 0; |
theDelegate = delegate; |
theUserData = userData; |
theSocket4 = NULL; |
theSource4 = NULL; |
theSocket6 = NULL; |
theSource6 = NULL; |
theRunLoop = NULL; |
theReadStream = NULL; |
theWriteStream = NULL; |
theConnectTimer = nil; |
theReadQueue = [[NSMutableArray alloc] initWithCapacity:READQUEUE_CAPACITY]; |
theCurrentRead = nil; |
theReadTimer = nil; |
partialReadBuffer = [[NSMutableData alloc] initWithCapacity:READALL_CHUNKSIZE]; |
theWriteQueue = [[NSMutableArray alloc] initWithCapacity:WRITEQUEUE_CAPACITY]; |
theCurrentWrite = nil; |
theWriteTimer = nil; |
// Socket context |
NSAssert(sizeof(CFSocketContext) == sizeof(CFStreamClientContext), @"CFSocketContext != CFStreamClientContext"); |
theContext.version = 0; |
theContext.info = self; |
theContext.retain = nil; |
theContext.release = nil; |
theContext.copyDescription = nil; |
// Default run loop modes |
theRunLoopModes = [[NSArray arrayWithObject:NSDefaultRunLoopMode] retain]; |
} |
return self; |
} |
// The socket may been initialized in a connected state and auto-released, so this should close it down cleanly. |
- (void)dealloc |
{ |
[self close]; |
[theReadQueue release]; |
[theWriteQueue release]; |
[theRunLoopModes release]; |
[partialReadBuffer release]; |
[NSObject cancelPreviousPerformRequestsWithTarget:self]; |
[super dealloc]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Accessors |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (long)userData |
{ |
return theUserData; |
} |
- (void)setUserData:(long)userData |
{ |
theUserData = userData; |
} |
- (id)delegate |
{ |
return theDelegate; |
} |
- (void)setDelegate:(id)delegate |
{ |
theDelegate = delegate; |
} |
- (BOOL)canSafelySetDelegate |
{ |
return ([theReadQueue count] == 0 && [theWriteQueue count] == 0 && theCurrentRead == nil && theCurrentWrite == nil); |
} |
- (CFSocketRef)getCFSocket |
{ |
if(theSocket4) |
return theSocket4; |
else |
return theSocket6; |
} |
- (CFReadStreamRef)getCFReadStream |
{ |
return theReadStream; |
} |
- (CFWriteStreamRef)getCFWriteStream |
{ |
return theWriteStream; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Progress |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (float)progressOfReadReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total |
{ |
// Check to make sure we're actually reading something right now, |
// and that the read packet isn't an AsyncSpecialPacket (upgrade to TLS). |
if (!theCurrentRead || ![theCurrentRead isKindOfClass:[AsyncReadPacket class]]) return NAN; |
// It's only possible to know the progress of our read if we're reading to a certain length |
// If we're reading to data, we of course have no idea when the data will arrive |
// If we're reading to timeout, then we have no idea when the next chunk of data will arrive. |
BOOL hasTotal = (theCurrentRead->readAllAvailableData == NO && theCurrentRead->term == nil); |
CFIndex d = theCurrentRead->bytesDone; |
CFIndex t = hasTotal ? [theCurrentRead->buffer length] : 0; |
if (tag != NULL) *tag = theCurrentRead->tag; |
if (done != NULL) *done = d; |
if (total != NULL) *total = t; |
float ratio = (float)d/(float)t; |
return isnan(ratio) ? 1.0F : ratio; // 0 of 0 bytes is 100% done. |
} |
- (float)progressOfWriteReturningTag:(long *)tag bytesDone:(CFIndex *)done total:(CFIndex *)total |
{ |
// Check to make sure we're actually writing something right now, |
// and that the write packet isn't an AsyncSpecialPacket (upgrade to TLS). |
if (!theCurrentWrite || ![theCurrentWrite isKindOfClass:[AsyncWritePacket class]]) return NAN; |
CFIndex d = theCurrentWrite->bytesDone; |
CFIndex t = [theCurrentWrite->buffer length]; |
if (tag != NULL) *tag = theCurrentWrite->tag; |
if (done != NULL) *done = d; |
if (total != NULL) *total = t; |
return (float)d/(float)t; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Run Loop |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void)runLoopAddSource:(CFRunLoopSourceRef)source |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFRunLoopAddSource(theRunLoop, source, runLoopMode); |
} |
} |
- (void)runLoopRemoveSource:(CFRunLoopSourceRef)source |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFRunLoopRemoveSource(theRunLoop, source, runLoopMode); |
} |
} |
- (void)runLoopAddTimer:(NSTimer *)timer |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFRunLoopAddTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); |
} |
} |
- (void)runLoopRemoveTimer:(NSTimer *)timer |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFRunLoopRemoveTimer(theRunLoop, (CFRunLoopTimerRef)timer, runLoopMode); |
} |
} |
- (void)runLoopUnscheduleReadStream |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFReadStreamUnscheduleFromRunLoop(theReadStream, theRunLoop, runLoopMode); |
} |
CFReadStreamSetClient(theReadStream, kCFStreamEventNone, NULL, NULL); |
} |
- (void)runLoopUnscheduleWriteStream |
{ |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFWriteStreamUnscheduleFromRunLoop(theWriteStream, theRunLoop, runLoopMode); |
} |
CFWriteStreamSetClient(theWriteStream, kCFStreamEventNone, NULL, NULL); |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Configuration |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* See the header file for a full explanation of pre-buffering. |
**/ |
- (void)enablePreBuffering |
{ |
theFlags |= kEnablePreBuffering; |
} |
/** |
* See the header file for a full explanation of this method. |
**/ |
- (BOOL)moveToRunLoop:(NSRunLoop *)runLoop |
{ |
NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), |
@"moveToRunLoop must be called from within the current RunLoop!"); |
if(runLoop == nil) |
{ |
return NO; |
} |
if(theRunLoop == [runLoop getCFRunLoop]) |
{ |
return YES; |
} |
[NSObject cancelPreviousPerformRequestsWithTarget:self]; |
theFlags &= ~kDequeueReadScheduled; |
theFlags &= ~kDequeueWriteScheduled; |
if(theReadStream && theWriteStream) |
{ |
[self runLoopUnscheduleReadStream]; |
[self runLoopUnscheduleWriteStream]; |
} |
if(theSource4) [self runLoopRemoveSource:theSource4]; |
if(theSource6) [self runLoopRemoveSource:theSource6]; |
// We do not retain the timers - they get retained by the runloop when we add them as a source. |
// Since we're about to remove them as a source, we retain now, and release again below. |
[theReadTimer retain]; |
[theWriteTimer retain]; |
if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; |
if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; |
theRunLoop = [runLoop getCFRunLoop]; |
if(theReadTimer) [self runLoopAddTimer:theReadTimer]; |
if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; |
// Release timers since we retained them above |
[theReadTimer release]; |
[theWriteTimer release]; |
if(theSource4) [self runLoopAddSource:theSource4]; |
if(theSource6) [self runLoopAddSource:theSource6]; |
if(theReadStream && theWriteStream) |
{ |
if(![self attachStreamsToRunLoop:runLoop error:nil]) |
{ |
return NO; |
} |
} |
[runLoop performSelector:@selector(maybeDequeueRead) target:self argument:nil order:0 modes:theRunLoopModes]; |
[runLoop performSelector:@selector(maybeDequeueWrite) target:self argument:nil order:0 modes:theRunLoopModes]; |
[runLoop performSelector:@selector(maybeScheduleDisconnect) target:self argument:nil order:0 modes:theRunLoopModes]; |
return YES; |
} |
/** |
* See the header file for a full explanation of this method. |
**/ |
- (BOOL)setRunLoopModes:(NSArray *)runLoopModes |
{ |
NSAssert((theRunLoop == NULL) || (theRunLoop == CFRunLoopGetCurrent()), |
@"setRunLoopModes must be called from within the current RunLoop!"); |
if([runLoopModes count] == 0) |
{ |
return NO; |
} |
if([theRunLoopModes isEqualToArray:runLoopModes]) |
{ |
return YES; |
} |
[NSObject cancelPreviousPerformRequestsWithTarget:self]; |
theFlags &= ~kDequeueReadScheduled; |
theFlags &= ~kDequeueWriteScheduled; |
if(theReadStream && theWriteStream) |
{ |
[self runLoopUnscheduleReadStream]; |
[self runLoopUnscheduleWriteStream]; |
} |
if(theSource4) [self runLoopRemoveSource:theSource4]; |
if(theSource6) [self runLoopRemoveSource:theSource6]; |
// We do not retain the timers - they get retained by the runloop when we add them as a source. |
// Since we're about to remove them as a source, we retain now, and release again below. |
[theReadTimer retain]; |
[theWriteTimer retain]; |
if(theReadTimer) [self runLoopRemoveTimer:theReadTimer]; |
if(theWriteTimer) [self runLoopRemoveTimer:theWriteTimer]; |
[theRunLoopModes release]; |
theRunLoopModes = [runLoopModes copy]; |
if(theReadTimer) [self runLoopAddTimer:theReadTimer]; |
if(theWriteTimer) [self runLoopAddTimer:theWriteTimer]; |
// Release timers since we retained them above |
[theReadTimer release]; |
[theWriteTimer release]; |
if(theSource4) [self runLoopAddSource:theSource4]; |
if(theSource6) [self runLoopAddSource:theSource6]; |
if(theReadStream && theWriteStream) |
{ |
// Note: theRunLoop variable is a CFRunLoop, and NSRunLoop is NOT toll-free bridged with CFRunLoop. |
// So we cannot pass theRunLoop to the method below, which is expecting a NSRunLoop parameter. |
// Instead we pass nil, which will result in the method properly using the current run loop. |
if(![self attachStreamsToRunLoop:nil error:nil]) |
{ |
return NO; |
} |
} |
[self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
[self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
[self performSelector:@selector(maybeScheduleDisconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
return YES; |
} |
- (NSArray *)runLoopModes |
{ |
return [[theRunLoopModes retain] autorelease]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Accepting |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (BOOL)acceptOnPort:(UInt16)port error:(NSError **)errPtr |
{ |
return [self acceptOnInterface:nil port:port error:errPtr]; |
} |
/** |
* To accept on a certain interface, pass the address to accept on. |
* To accept on any interface, pass nil or an empty string. |
* To accept only connections from localhost pass "localhost" or "loopback". |
**/ |
- (BOOL)acceptOnInterface:(NSString *)interface port:(UInt16)port error:(NSError **)errPtr |
{ |
if (theDelegate == NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to accept without a delegate. Set a delegate first."]; |
} |
if (theSocket4 != NULL || theSocket6 != NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to accept while connected or accepting connections. Disconnect first."]; |
} |
// Set up the listen sockaddr structs if needed. |
NSData *address4 = nil, *address6 = nil; |
if(interface == nil || ([interface length] == 0)) |
{ |
// Accept on ANY address |
struct sockaddr_in nativeAddr4; |
nativeAddr4.sin_len = sizeof(struct sockaddr_in); |
nativeAddr4.sin_family = AF_INET; |
nativeAddr4.sin_port = htons(port); |
nativeAddr4.sin_addr.s_addr = htonl(INADDR_ANY); |
memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); |
struct sockaddr_in6 nativeAddr6; |
nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); |
nativeAddr6.sin6_family = AF_INET6; |
nativeAddr6.sin6_port = htons(port); |
nativeAddr6.sin6_flowinfo = 0; |
nativeAddr6.sin6_addr = in6addr_any; |
nativeAddr6.sin6_scope_id = 0; |
// Wrap the native address structures for CFSocketSetAddress. |
address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; |
address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; |
} |
else if([interface isEqualToString:@"localhost"] || [interface isEqualToString:@"loopback"]) |
{ |
// Accept only on LOOPBACK address |
struct sockaddr_in nativeAddr4; |
nativeAddr4.sin_len = sizeof(struct sockaddr_in); |
nativeAddr4.sin_family = AF_INET; |
nativeAddr4.sin_port = htons(port); |
nativeAddr4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); |
memset(&(nativeAddr4.sin_zero), 0, sizeof(nativeAddr4.sin_zero)); |
struct sockaddr_in6 nativeAddr6; |
nativeAddr6.sin6_len = sizeof(struct sockaddr_in6); |
nativeAddr6.sin6_family = AF_INET6; |
nativeAddr6.sin6_port = htons(port); |
nativeAddr6.sin6_flowinfo = 0; |
nativeAddr6.sin6_addr = in6addr_loopback; |
nativeAddr6.sin6_scope_id = 0; |
// Wrap the native address structures for CFSocketSetAddress. |
address4 = [NSData dataWithBytes:&nativeAddr4 length:sizeof(nativeAddr4)]; |
address6 = [NSData dataWithBytes:&nativeAddr6 length:sizeof(nativeAddr6)]; |
} |
else |
{ |
NSString *portStr = [NSString stringWithFormat:@"%hu", port]; |
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
@synchronized (getaddrinfoLock) |
#endif |
{ |
struct addrinfo hints, *res, *res0; |
memset(&hints, 0, sizeof(hints)); |
hints.ai_family = PF_UNSPEC; |
hints.ai_socktype = SOCK_STREAM; |
hints.ai_protocol = IPPROTO_TCP; |
hints.ai_flags = AI_PASSIVE; |
int error = getaddrinfo([interface UTF8String], [portStr UTF8String], &hints, &res0); |
if(error) |
{ |
if(errPtr) |
{ |
NSString *errMsg = [NSString stringWithCString:gai_strerror(error) encoding:NSASCIIStringEncoding]; |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
*errPtr = [NSError errorWithDomain:@"kCFStreamErrorDomainNetDB" code:error userInfo:info]; |
} |
} |
for(res = res0; res; res = res->ai_next) |
{ |
if(!address4 && (res->ai_family == AF_INET)) |
{ |
// Found IPv4 address |
// Wrap the native address structures for CFSocketSetAddress. |
address4 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; |
} |
else if(!address6 && (res->ai_family == AF_INET6)) |
{ |
// Found IPv6 address |
// Wrap the native address structures for CFSocketSetAddress. |
address6 = [NSData dataWithBytes:res->ai_addr length:res->ai_addrlen]; |
} |
} |
freeaddrinfo(res0); |
} |
if(!address4 && !address6) return NO; |
} |
// Create the sockets. |
if (address4) |
{ |
theSocket4 = [self newAcceptSocketForAddress:address4 error:errPtr]; |
if (theSocket4 == NULL) goto Failed; |
} |
if (address6) |
{ |
theSocket6 = [self newAcceptSocketForAddress:address6 error:errPtr]; |
// Note: The iPhone doesn't currently support IPv6 |
#if !TARGET_OS_IPHONE |
if (theSocket6 == NULL) goto Failed; |
#endif |
} |
// Attach the sockets to the run loop so that callback methods work |
[self attachSocketsToRunLoop:nil error:nil]; |
// Set the SO_REUSEADDR flags. |
int reuseOn = 1; |
if (theSocket4) setsockopt(CFSocketGetNative(theSocket4), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); |
if (theSocket6) setsockopt(CFSocketGetNative(theSocket6), SOL_SOCKET, SO_REUSEADDR, &reuseOn, sizeof(reuseOn)); |
// Set the local bindings which causes the sockets to start listening. |
CFSocketError err; |
if (theSocket4) |
{ |
err = CFSocketSetAddress (theSocket4, (CFDataRef)address4); |
if (err != kCFSocketSuccess) goto Failed; |
//NSLog(@"theSocket4: %hu", [self localPort:theSocket4]); |
} |
if(port == 0 && theSocket4 && theSocket6) |
{ |
// The user has passed in port 0, which means he wants to allow the kernel to choose the port for them |
// However, the kernel will choose a different port for both theSocket4 and theSocket6 |
// So we grab the port the kernel choose for theSocket4, and set it as the port for theSocket6 |
UInt16 chosenPort = [self localPort:theSocket4]; |
struct sockaddr_in6 *pSockAddr6 = (struct sockaddr_in6 *)[address6 bytes]; |
pSockAddr6->sin6_port = htons(chosenPort); |
} |
if (theSocket6) |
{ |
err = CFSocketSetAddress (theSocket6, (CFDataRef)address6); |
if (err != kCFSocketSuccess) goto Failed; |
//NSLog(@"theSocket6: %hu", [self localPort:theSocket6]); |
} |
theFlags |= kDidPassConnectMethod; |
return YES; |
Failed: |
if(errPtr) *errPtr = [self getSocketError]; |
if(theSocket4 != NULL) |
{ |
CFSocketInvalidate(theSocket4); |
CFRelease(theSocket4); |
theSocket4 = NULL; |
} |
if(theSocket6 != NULL) |
{ |
CFSocketInvalidate(theSocket6); |
CFRelease(theSocket6); |
theSocket6 = NULL; |
} |
return NO; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Connecting |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (BOOL)connectToHost:(NSString*)hostname onPort:(UInt16)port error:(NSError **)errPtr |
{ |
return [self connectToHost:hostname onPort:port withTimeout:-1 error:errPtr]; |
} |
/** |
* This method creates an initial CFReadStream and CFWriteStream to the given host on the given port. |
* The connection is then opened, and the corresponding CFSocket will be extracted after the connection succeeds. |
* |
* Thus the delegate will have access to the CFReadStream and CFWriteStream prior to connection, |
* specifically in the onSocketWillConnect: method. |
**/ |
- (BOOL)connectToHost:(NSString *)hostname |
onPort:(UInt16)port |
withTimeout:(NSTimeInterval)timeout |
error:(NSError **)errPtr |
{ |
if(theDelegate == NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to connect without a delegate. Set a delegate first."]; |
} |
if(theSocket4 != NULL || theSocket6 != NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; |
} |
if(![self createStreamsToHost:hostname onPort:port error:errPtr]) goto Failed; |
if(![self attachStreamsToRunLoop:nil error:errPtr]) goto Failed; |
if(![self configureStreamsAndReturnError:errPtr]) goto Failed; |
if(![self openStreamsAndReturnError:errPtr]) goto Failed; |
[self startConnectTimeout:timeout]; |
theFlags |= kDidPassConnectMethod; |
return YES; |
Failed: |
[self close]; |
return NO; |
} |
- (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
{ |
return [self connectToAddress:remoteAddr withTimeout:-1 error:errPtr]; |
} |
/** |
* This method creates an initial CFSocket to the given address. |
* The connection is then opened, and the corresponding CFReadStream and CFWriteStream will be |
* created from the low-level sockets after the connection succeeds. |
* |
* Thus the delegate will have access to the CFSocket and CFSocketNativeHandle (BSD socket) prior to connection, |
* specifically in the onSocketWillConnect: method. |
* |
* Note: The NSData parameter is expected to be a sockaddr structure. For example, an NSData object returned from |
* NSNetservice addresses method. |
* If you have an existing struct sockaddr you can convert it to an NSData object like so: |
* struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; |
* struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; |
**/ |
- (BOOL)connectToAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout error:(NSError **)errPtr |
{ |
if (theDelegate == NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to connect without a delegate. Set a delegate first."]; |
} |
if (theSocket4 != NULL || theSocket6 != NULL) |
{ |
[NSException raise:AsyncSocketException |
format:@"Attempting to connect while connected or accepting connections. Disconnect first."]; |
} |
if(![self createSocketForAddress:remoteAddr error:errPtr]) goto Failed; |
if(![self attachSocketsToRunLoop:nil error:errPtr]) goto Failed; |
if(![self configureSocketAndReturnError:errPtr]) goto Failed; |
if(![self connectSocketToAddress:remoteAddr error:errPtr]) goto Failed; |
[self startConnectTimeout:timeout]; |
theFlags |= kDidPassConnectMethod; |
return YES; |
Failed: |
[self close]; |
return NO; |
} |
- (void)startConnectTimeout:(NSTimeInterval)timeout |
{ |
if(timeout >= 0.0) |
{ |
theConnectTimer = [NSTimer timerWithTimeInterval:timeout |
target:self |
selector:@selector(doConnectTimeout:) |
userInfo:nil |
repeats:NO]; |
[self runLoopAddTimer:theConnectTimer]; |
} |
} |
- (void)endConnectTimeout |
{ |
[theConnectTimer invalidate]; |
theConnectTimer = nil; |
} |
- (void)doConnectTimeout:(NSTimer *)timer |
{ |
[self endConnectTimeout]; |
[self closeWithError:[self getConnectTimeoutError]]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Socket Implementation |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* Creates the accept sockets. |
* Returns true if either IPv4 or IPv6 is created. |
* If either is missing, an error is returned (even though the method may return true). |
**/ |
- (CFSocketRef)newAcceptSocketForAddress:(NSData *)addr error:(NSError **)errPtr |
{ |
struct sockaddr *pSockAddr = (struct sockaddr *)[addr bytes]; |
int addressFamily = pSockAddr->sa_family; |
CFSocketRef theSocket = CFSocketCreate(kCFAllocatorDefault, |
addressFamily, |
SOCK_STREAM, |
0, |
kCFSocketAcceptCallBack, // Callback flags |
(CFSocketCallBack)&MyCFSocketCallback, // Callback method |
&theContext); |
if(theSocket == NULL) |
{ |
if(errPtr) *errPtr = [self getSocketError]; |
} |
return theSocket; |
} |
- (BOOL)createSocketForAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
{ |
struct sockaddr *pSockAddr = (struct sockaddr *)[remoteAddr bytes]; |
if(pSockAddr->sa_family == AF_INET) |
{ |
theSocket4 = CFSocketCreate(NULL, // Default allocator |
PF_INET, // Protocol Family |
SOCK_STREAM, // Socket Type |
IPPROTO_TCP, // Protocol |
kCFSocketConnectCallBack, // Callback flags |
(CFSocketCallBack)&MyCFSocketCallback, // Callback method |
&theContext); // Socket Context |
if(theSocket4 == NULL) |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
} |
else if(pSockAddr->sa_family == AF_INET6) |
{ |
theSocket6 = CFSocketCreate(NULL, // Default allocator |
PF_INET6, // Protocol Family |
SOCK_STREAM, // Socket Type |
IPPROTO_TCP, // Protocol |
kCFSocketConnectCallBack, // Callback flags |
(CFSocketCallBack)&MyCFSocketCallback, // Callback method |
&theContext); // Socket Context |
if(theSocket6 == NULL) |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
} |
else |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
return YES; |
} |
/** |
* Adds the CFSocket's to the run-loop so that callbacks will work properly. |
**/ |
- (BOOL)attachSocketsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr |
{ |
// Get the CFRunLoop to which the socket should be attached. |
theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; |
if(theSocket4) |
{ |
theSource4 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket4, 0); |
[self runLoopAddSource:theSource4]; |
} |
if(theSocket6) |
{ |
theSource6 = CFSocketCreateRunLoopSource (kCFAllocatorDefault, theSocket6, 0); |
[self runLoopAddSource:theSource6]; |
} |
return YES; |
} |
/** |
* Allows the delegate method to configure the CFSocket or CFNativeSocket as desired before we connect. |
* Note that the CFReadStream and CFWriteStream will not be available until after the connection is opened. |
**/ |
- (BOOL)configureSocketAndReturnError:(NSError **)errPtr |
{ |
// Call the delegate method for further configuration. |
if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) |
{ |
if([theDelegate onSocketWillConnect:self] == NO) |
{ |
if (errPtr) *errPtr = [self getAbortError]; |
return NO; |
} |
} |
return YES; |
} |
- (BOOL)connectSocketToAddress:(NSData *)remoteAddr error:(NSError **)errPtr |
{ |
// Start connecting to the given address in the background |
// The MyCFSocketCallback method will be called when the connection succeeds or fails |
if(theSocket4) |
{ |
CFSocketError err = CFSocketConnectToAddress(theSocket4, (CFDataRef)remoteAddr, -1); |
if(err != kCFSocketSuccess) |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
} |
else if(theSocket6) |
{ |
CFSocketError err = CFSocketConnectToAddress(theSocket6, (CFDataRef)remoteAddr, -1); |
if(err != kCFSocketSuccess) |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
} |
return YES; |
} |
/** |
* Attempt to make the new socket. |
* If an error occurs, ignore this event. |
**/ |
- (void)doAcceptWithSocket:(CFSocketNativeHandle)newNative |
{ |
// New socket inherits same delegate and run loop modes. |
// Note: We use [self class] to support subclassing AsyncSocket. |
AsyncSocket *newSocket = [[[[self class] alloc] initWithDelegate:theDelegate] autorelease]; |
[newSocket setRunLoopModes:theRunLoopModes]; |
if(newSocket) |
{ |
if ([theDelegate respondsToSelector:@selector(onSocket:didAcceptNewSocket:)]) |
[theDelegate onSocket:self didAcceptNewSocket:newSocket]; |
NSRunLoop *runLoop = nil; |
if ([theDelegate respondsToSelector:@selector(onSocket:wantsRunLoopForNewSocket:)]) |
runLoop = [theDelegate onSocket:self wantsRunLoopForNewSocket:newSocket]; |
BOOL pass = YES; |
if(pass && ![newSocket createStreamsFromNative:newNative error:nil]) pass = NO; |
if(pass && ![newSocket attachStreamsToRunLoop:runLoop error:nil]) pass = NO; |
if(pass && ![newSocket configureStreamsAndReturnError:nil]) pass = NO; |
if(pass && ![newSocket openStreamsAndReturnError:nil]) pass = NO; |
if(pass) |
newSocket->theFlags |= kDidPassConnectMethod; |
else { |
// No NSError, but errors will still get logged from the above functions. |
[newSocket close]; |
} |
} |
} |
/** |
* Description forthcoming... |
**/ |
- (void)doSocketOpen:(CFSocketRef)sock withCFSocketError:(CFSocketError)socketError |
{ |
NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); |
if(socketError == kCFSocketTimeout || socketError == kCFSocketError) |
{ |
[self closeWithError:[self getSocketError]]; |
return; |
} |
// Get the underlying native (BSD) socket |
CFSocketNativeHandle nativeSocket = CFSocketGetNative(sock); |
// Setup the socket so that invalidating the socket will not close the native socket |
CFSocketSetSocketFlags(sock, 0); |
// Invalidate and release the CFSocket - All we need from here on out is the nativeSocket |
// Note: If we don't invalidate the socket (leaving the native socket open) |
// then theReadStream and theWriteStream won't function properly. |
// Specifically, their callbacks won't work, with the exception of kCFStreamEventOpenCompleted. |
// I'm not entirely sure why this is, but I'm guessing that events on the socket fire to the CFSocket we created, |
// as opposed to the CFReadStream/CFWriteStream. |
CFSocketInvalidate(sock); |
CFRelease(sock); |
theSocket4 = NULL; |
theSocket6 = NULL; |
NSError *err; |
BOOL pass = YES; |
if(pass && ![self createStreamsFromNative:nativeSocket error:&err]) pass = NO; |
if(pass && ![self attachStreamsToRunLoop:nil error:&err]) pass = NO; |
if(pass && ![self openStreamsAndReturnError:&err]) pass = NO; |
if(!pass) |
{ |
[self closeWithError:err]; |
} |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Stream Implementation |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* Creates the CFReadStream and CFWriteStream from the given native socket. |
* The CFSocket may be extracted from either stream after the streams have been opened. |
* |
* Note: The given native socket must already be connected! |
**/ |
- (BOOL)createStreamsFromNative:(CFSocketNativeHandle)native error:(NSError **)errPtr |
{ |
// Create the socket & streams. |
CFStreamCreatePairWithSocket(kCFAllocatorDefault, native, &theReadStream, &theWriteStream); |
if (theReadStream == NULL || theWriteStream == NULL) |
{ |
NSError *err = [self getStreamError]; |
NSLog (@"AsyncSocket %p couldn't create streams from accepted socket: %@", self, err); |
if (errPtr) *errPtr = err; |
return NO; |
} |
// Ensure the CF & BSD socket is closed when the streams are closed. |
CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
return YES; |
} |
/** |
* Creates the CFReadStream and CFWriteStream from the given hostname and port number. |
* The CFSocket may be extracted from either stream after the streams have been opened. |
**/ |
- (BOOL)createStreamsToHost:(NSString *)hostname onPort:(UInt16)port error:(NSError **)errPtr |
{ |
// Create the socket & streams. |
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (CFStringRef)hostname, port, &theReadStream, &theWriteStream); |
if (theReadStream == NULL || theWriteStream == NULL) |
{ |
if (errPtr) *errPtr = [self getStreamError]; |
return NO; |
} |
// Ensure the CF & BSD socket is closed when the streams are closed. |
CFReadStreamSetProperty(theReadStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue); |
return YES; |
} |
- (BOOL)attachStreamsToRunLoop:(NSRunLoop *)runLoop error:(NSError **)errPtr |
{ |
// Get the CFRunLoop to which the socket should be attached. |
theRunLoop = (runLoop == nil) ? CFRunLoopGetCurrent() : [runLoop getCFRunLoop]; |
// Setup read stream callbacks |
CFOptionFlags readStreamEvents = kCFStreamEventHasBytesAvailable | |
kCFStreamEventErrorOccurred | |
kCFStreamEventEndEncountered | |
kCFStreamEventOpenCompleted; |
if (!CFReadStreamSetClient(theReadStream, |
readStreamEvents, |
(CFReadStreamClientCallBack)&MyCFReadStreamCallback, |
(CFStreamClientContext *)(&theContext))) |
{ |
NSError *err = [self getStreamError]; |
NSLog (@"AsyncSocket %p couldn't attach read stream to run-loop,", self); |
NSLog (@"Error: %@", err); |
if (errPtr) *errPtr = err; |
return NO; |
} |
// Setup write stream callbacks |
CFOptionFlags writeStreamEvents = kCFStreamEventCanAcceptBytes | |
kCFStreamEventErrorOccurred | |
kCFStreamEventEndEncountered | |
kCFStreamEventOpenCompleted; |
if (!CFWriteStreamSetClient (theWriteStream, |
writeStreamEvents, |
(CFWriteStreamClientCallBack)&MyCFWriteStreamCallback, |
(CFStreamClientContext *)(&theContext))) |
{ |
NSError *err = [self getStreamError]; |
NSLog (@"AsyncSocket %p couldn't attach write stream to run-loop,", self); |
NSLog (@"Error: %@", err); |
if (errPtr) *errPtr = err; |
return NO; |
} |
// Add read and write streams to run loop |
unsigned i, count = [theRunLoopModes count]; |
for(i = 0; i < count; i++) |
{ |
CFStringRef runLoopMode = (CFStringRef)[theRunLoopModes objectAtIndex:i]; |
CFReadStreamScheduleWithRunLoop(theReadStream, theRunLoop, runLoopMode); |
CFWriteStreamScheduleWithRunLoop(theWriteStream, theRunLoop, runLoopMode); |
} |
return YES; |
} |
/** |
* Allows the delegate method to configure the CFReadStream and/or CFWriteStream as desired before we connect. |
* Note that the CFSocket and CFNativeSocket will not be available until after the connection is opened. |
**/ |
- (BOOL)configureStreamsAndReturnError:(NSError **)errPtr |
{ |
// Call the delegate method for further configuration. |
if([theDelegate respondsToSelector:@selector(onSocketWillConnect:)]) |
{ |
if([theDelegate onSocketWillConnect:self] == NO) |
{ |
if (errPtr) *errPtr = [self getAbortError]; |
return NO; |
} |
} |
return YES; |
} |
- (BOOL)openStreamsAndReturnError:(NSError **)errPtr |
{ |
BOOL pass = YES; |
if(pass && !CFReadStreamOpen (theReadStream)) |
{ |
NSLog (@"AsyncSocket %p couldn't open read stream,", self); |
pass = NO; |
} |
if(pass && !CFWriteStreamOpen (theWriteStream)) |
{ |
NSLog (@"AsyncSocket %p couldn't open write stream,", self); |
pass = NO; |
} |
if(!pass) |
{ |
if (errPtr) *errPtr = [self getStreamError]; |
} |
return pass; |
} |
/** |
* Called when read or write streams open. |
* When the socket is connected and both streams are open, consider the AsyncSocket instance to be ready. |
**/ |
- (void)doStreamOpen |
{ |
NSError *err = nil; |
if ((theFlags & kDidCompleteOpenForRead) && (theFlags & kDidCompleteOpenForWrite)) |
{ |
// Get the socket. |
if (![self setSocketFromStreamsAndReturnError: &err]) |
{ |
NSLog (@"AsyncSocket %p couldn't get socket from streams, %@. Disconnecting.", self, err); |
[self closeWithError:err]; |
return; |
} |
// Stop the connection attempt timeout timer |
[self endConnectTimeout]; |
if ([theDelegate respondsToSelector:@selector(onSocket:didConnectToHost:port:)]) |
{ |
[theDelegate onSocket:self didConnectToHost:[self connectedHost] port:[self connectedPort]]; |
} |
// Immediately deal with any already-queued requests. |
[self maybeDequeueRead]; |
[self maybeDequeueWrite]; |
} |
} |
- (BOOL)setSocketFromStreamsAndReturnError:(NSError **)errPtr |
{ |
// Get the CFSocketNativeHandle from theReadStream |
CFSocketNativeHandle native; |
CFDataRef nativeProp = CFReadStreamCopyProperty(theReadStream, kCFStreamPropertySocketNativeHandle); |
if(nativeProp == NULL) |
{ |
if (errPtr) *errPtr = [self getStreamError]; |
return NO; |
} |
CFDataGetBytes(nativeProp, CFRangeMake(0, CFDataGetLength(nativeProp)), (UInt8 *)&native); |
CFRelease(nativeProp); |
CFSocketRef theSocket = CFSocketCreateWithNative(kCFAllocatorDefault, native, 0, NULL, NULL); |
if(theSocket == NULL) |
{ |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
// Determine whether the connection was IPv4 or IPv6 |
CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); |
if(peeraddr == NULL) |
{ |
NSLog(@"AsyncSocket couldn't determine IP version of socket"); |
CFRelease(theSocket); |
if (errPtr) *errPtr = [self getSocketError]; |
return NO; |
} |
struct sockaddr *sa = (struct sockaddr *)CFDataGetBytePtr(peeraddr); |
if(sa->sa_family == AF_INET) |
{ |
theSocket4 = theSocket; |
} |
else |
{ |
theSocket6 = theSocket; |
} |
CFRelease(peeraddr); |
return YES; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Disconnect Implementation |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
// Sends error message and disconnects |
- (void)closeWithError:(NSError *)err |
{ |
theFlags |= kClosingWithError; |
if (theFlags & kDidPassConnectMethod) |
{ |
// Try to salvage what data we can. |
[self recoverUnreadData]; |
// Let the delegate know, so it can try to recover if it likes. |
if ([theDelegate respondsToSelector:@selector(onSocket:willDisconnectWithError:)]) |
{ |
[theDelegate onSocket:self willDisconnectWithError:err]; |
} |
} |
[self close]; |
} |
// Prepare partially read data for recovery. |
- (void)recoverUnreadData |
{ |
if(theCurrentRead != nil) |
{ |
// We never finished the current read. |
// Check to see if it's a normal read packet (not AsyncSpecialPacket) and if it had read anything yet. |
if(([theCurrentRead isKindOfClass:[AsyncReadPacket class]]) && (theCurrentRead->bytesDone > 0)) |
{ |
// We need to move its data into the front of the partial read buffer. |
[partialReadBuffer replaceBytesInRange:NSMakeRange(0, 0) |
withBytes:[theCurrentRead->buffer bytes] |
length:theCurrentRead->bytesDone]; |
} |
} |
[self emptyQueues]; |
} |
- (void)emptyQueues |
{ |
if (theCurrentRead != nil) [self endCurrentRead]; |
if (theCurrentWrite != nil) [self endCurrentWrite]; |
[theReadQueue removeAllObjects]; |
[theWriteQueue removeAllObjects]; |
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueRead) object:nil]; |
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(maybeDequeueWrite) object:nil]; |
theFlags &= ~kDequeueReadScheduled; |
theFlags &= ~kDequeueWriteScheduled; |
} |
/** |
* Disconnects. This is called for both error and clean disconnections. |
**/ |
- (void)close |
{ |
// Empty queues |
[self emptyQueues]; |
// Clear partialReadBuffer (pre-buffer and also unreadData buffer in case of error) |
[partialReadBuffer replaceBytesInRange:NSMakeRange(0, [partialReadBuffer length]) withBytes:NULL length:0]; |
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(disconnect) object:nil]; |
// Stop the connection attempt timeout timer |
if (theConnectTimer != nil) |
{ |
[self endConnectTimeout]; |
} |
// Close streams. |
if (theReadStream != NULL) |
{ |
[self runLoopUnscheduleReadStream]; |
CFReadStreamClose(theReadStream); |
CFRelease(theReadStream); |
theReadStream = NULL; |
} |
if (theWriteStream != NULL) |
{ |
[self runLoopUnscheduleWriteStream]; |
CFWriteStreamClose(theWriteStream); |
CFRelease(theWriteStream); |
theWriteStream = NULL; |
} |
// Close sockets. |
if (theSocket4 != NULL) |
{ |
CFSocketInvalidate (theSocket4); |
CFRelease (theSocket4); |
theSocket4 = NULL; |
} |
if (theSocket6 != NULL) |
{ |
CFSocketInvalidate (theSocket6); |
CFRelease (theSocket6); |
theSocket6 = NULL; |
} |
if (theSource4 != NULL) |
{ |
[self runLoopRemoveSource:theSource4]; |
CFRelease (theSource4); |
theSource4 = NULL; |
} |
if (theSource6 != NULL) |
{ |
[self runLoopRemoveSource:theSource6]; |
CFRelease (theSource6); |
theSource6 = NULL; |
} |
theRunLoop = NULL; |
// If the client has passed the connect/accept method, then the connection has at least begun. |
// Notify delegate that it is now ending. |
BOOL shouldCallDelegate = (theFlags & kDidPassConnectMethod); |
// Clear all flags (except the pre-buffering flag, which should remain as is) |
theFlags &= kEnablePreBuffering; |
if (shouldCallDelegate) |
{ |
if ([theDelegate respondsToSelector: @selector(onSocketDidDisconnect:)]) |
{ |
[theDelegate onSocketDidDisconnect:self]; |
} |
} |
// Do not access any instance variables after calling onSocketDidDisconnect. |
// This gives the delegate freedom to release us without returning here and crashing. |
} |
/** |
* Disconnects immediately. Any pending reads or writes are dropped. |
**/ |
- (void)disconnect |
{ |
[self close]; |
} |
/** |
* Diconnects after all pending reads have completed. |
**/ |
- (void)disconnectAfterReading |
{ |
theFlags |= (kForbidReadsWrites | kDisconnectAfterReads); |
[self maybeScheduleDisconnect]; |
} |
/** |
* Disconnects after all pending writes have completed. |
**/ |
- (void)disconnectAfterWriting |
{ |
theFlags |= (kForbidReadsWrites | kDisconnectAfterWrites); |
[self maybeScheduleDisconnect]; |
} |
/** |
* Disconnects after all pending reads and writes have completed. |
**/ |
- (void)disconnectAfterReadingAndWriting |
{ |
theFlags |= (kForbidReadsWrites | kDisconnectAfterReads | kDisconnectAfterWrites); |
[self maybeScheduleDisconnect]; |
} |
/** |
* Schedules a call to disconnect if possible. |
* That is, if all writes have completed, and we're set to disconnect after writing, |
* or if all reads have completed, and we're set to disconnect after reading. |
**/ |
- (void)maybeScheduleDisconnect |
{ |
BOOL shouldDisconnect = NO; |
if(theFlags & kDisconnectAfterReads) |
{ |
if(([theReadQueue count] == 0) && (theCurrentRead == nil)) |
{ |
if(theFlags & kDisconnectAfterWrites) |
{ |
if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
{ |
shouldDisconnect = YES; |
} |
} |
else |
{ |
shouldDisconnect = YES; |
} |
} |
} |
else if(theFlags & kDisconnectAfterWrites) |
{ |
if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
{ |
shouldDisconnect = YES; |
} |
} |
if(shouldDisconnect) |
{ |
[self performSelector:@selector(disconnect) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
} |
} |
/** |
* In the event of an error, this method may be called during onSocket:willDisconnectWithError: to read |
* any data that's left on the socket. |
**/ |
- (NSData *)unreadData |
{ |
// Ensure this method will only return data in the event of an error |
if(!(theFlags & kClosingWithError)) return nil; |
if(theReadStream == NULL) return nil; |
CFIndex totalBytesRead = [partialReadBuffer length]; |
BOOL error = NO; |
while(!error && CFReadStreamHasBytesAvailable(theReadStream)) |
{ |
[partialReadBuffer increaseLengthBy:READALL_CHUNKSIZE]; |
// Number of bytes to read is space left in packet buffer. |
CFIndex bytesToRead = [partialReadBuffer length] - totalBytesRead; |
// Read data into packet buffer |
UInt8 *packetbuf = (UInt8 *)( [partialReadBuffer mutableBytes] + totalBytesRead ); |
CFIndex bytesRead = CFReadStreamRead(theReadStream, packetbuf, bytesToRead); |
// Check results |
if(bytesRead < 0) |
{ |
error = YES; |
} |
else |
{ |
totalBytesRead += bytesRead; |
} |
} |
[partialReadBuffer setLength:totalBytesRead]; |
return partialReadBuffer; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Errors |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
/** |
* Returns a standard error object for the current errno value. |
* Errno is used for low-level BSD socket errors. |
**/ |
- (NSError *)getErrnoError |
{ |
NSString *errorMsg = [NSString stringWithUTF8String:strerror(errno)]; |
NSDictionary *userInfo = [NSDictionary dictionaryWithObject:errorMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:userInfo]; |
} |
/** |
* Returns a standard error message for a CFSocket error. |
* Unfortunately, CFSocket offers no feedback on its errors. |
**/ |
- (NSError *)getSocketError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCFSocketError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"General CFSocket error", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCFSocketError userInfo:info]; |
} |
- (NSError *)getStreamError |
{ |
CFStreamError err; |
if (theReadStream != NULL) |
{ |
err = CFReadStreamGetError (theReadStream); |
if (err.error != 0) return [self errorFromCFStreamError: err]; |
} |
if (theWriteStream != NULL) |
{ |
err = CFWriteStreamGetError (theWriteStream); |
if (err.error != 0) return [self errorFromCFStreamError: err]; |
} |
return nil; |
} |
/** |
* Returns a standard AsyncSocket abort error. |
**/ |
- (NSError *)getAbortError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketCanceledError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"Connection canceled", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketCanceledError userInfo:info]; |
} |
/** |
* Returns a standard AsyncSocket connect timeout error. |
**/ |
- (NSError *)getConnectTimeoutError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketConnectTimeoutError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"Attempt to connect to host timed out", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketConnectTimeoutError userInfo:info]; |
} |
/** |
* Returns a standard AsyncSocket maxed out error. |
**/ |
- (NSError *)getReadMaxedOutError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadMaxedOutError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"Read operation reached set maximum length", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadMaxedOutError userInfo:info]; |
} |
/** |
* Returns a standard AsyncSocket read timeout error. |
**/ |
- (NSError *)getReadTimeoutError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketReadTimeoutError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"Read operation timed out", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketReadTimeoutError userInfo:info]; |
} |
/** |
* Returns a standard AsyncSocket write timeout error. |
**/ |
- (NSError *)getWriteTimeoutError |
{ |
NSString *errMsg = NSLocalizedStringWithDefaultValue(@"AsyncSocketWriteTimeoutError", |
@"AsyncSocket", [NSBundle mainBundle], |
@"Write operation timed out", nil); |
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey]; |
return [NSError errorWithDomain:AsyncSocketErrorDomain code:AsyncSocketWriteTimeoutError userInfo:info]; |
} |
- (NSError *)errorFromCFStreamError:(CFStreamError)err |
{ |
if (err.domain == 0 && err.error == 0) return nil; |
// Can't use switch; these constants aren't int literals. |
NSString *domain = @"CFStreamError (unlisted domain)"; |
NSString *message = nil; |
if(err.domain == kCFStreamErrorDomainPOSIX) { |
domain = NSPOSIXErrorDomain; |
} |
else if(err.domain == kCFStreamErrorDomainMacOSStatus) { |
domain = NSOSStatusErrorDomain; |
} |
else if(err.domain == kCFStreamErrorDomainMach) { |
domain = NSMachErrorDomain; |
} |
else if(err.domain == kCFStreamErrorDomainNetDB) |
{ |
domain = @"kCFStreamErrorDomainNetDB"; |
message = [NSString stringWithCString:gai_strerror(err.error) encoding:NSASCIIStringEncoding]; |
} |
else if(err.domain == kCFStreamErrorDomainNetServices) { |
domain = @"kCFStreamErrorDomainNetServices"; |
} |
else if(err.domain == kCFStreamErrorDomainSOCKS) { |
domain = @"kCFStreamErrorDomainSOCKS"; |
} |
else if(err.domain == kCFStreamErrorDomainSystemConfiguration) { |
domain = @"kCFStreamErrorDomainSystemConfiguration"; |
} |
else if(err.domain == kCFStreamErrorDomainSSL) { |
domain = @"kCFStreamErrorDomainSSL"; |
} |
NSDictionary *info = nil; |
if(message != nil) |
{ |
info = [NSDictionary dictionaryWithObject:message forKey:NSLocalizedDescriptionKey]; |
} |
return [NSError errorWithDomain:domain code:err.error userInfo:info]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Diagnostics |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (BOOL)isConnected |
{ |
return [self isSocketConnected] && [self areStreamsConnected]; |
} |
- (NSString *)connectedHost |
{ |
if(theSocket4) |
return [self connectedHost:theSocket4]; |
else |
return [self connectedHost:theSocket6]; |
} |
- (UInt16)connectedPort |
{ |
if(theSocket4) |
return [self connectedPort:theSocket4]; |
else |
return [self connectedPort:theSocket6]; |
} |
- (NSString *)localHost |
{ |
if(theSocket4) |
return [self localHost:theSocket4]; |
else |
return [self localHost:theSocket6]; |
} |
- (UInt16)localPort |
{ |
if(theSocket4) |
return [self localPort:theSocket4]; |
else |
return [self localPort:theSocket6]; |
} |
- (NSString *)connectedHost:(CFSocketRef)theSocket |
{ |
if (theSocket == NULL) return nil; |
CFDataRef peeraddr; |
NSString *peerstr = nil; |
if((peeraddr = CFSocketCopyPeerAddress(theSocket))) |
{ |
peerstr = [self addressHost:peeraddr]; |
CFRelease (peeraddr); |
} |
return peerstr; |
} |
- (UInt16)connectedPort:(CFSocketRef)theSocket |
{ |
if (theSocket == NULL) return 0; |
CFDataRef peeraddr; |
UInt16 peerport = 0; |
if((peeraddr = CFSocketCopyPeerAddress(theSocket))) |
{ |
peerport = [self addressPort:peeraddr]; |
CFRelease (peeraddr); |
} |
return peerport; |
} |
- (NSString *)localHost:(CFSocketRef)theSocket |
{ |
if (theSocket == NULL) return nil; |
CFDataRef selfaddr; |
NSString *selfstr = nil; |
if((selfaddr = CFSocketCopyAddress(theSocket))) |
{ |
selfstr = [self addressHost:selfaddr]; |
CFRelease (selfaddr); |
} |
return selfstr; |
} |
- (UInt16)localPort:(CFSocketRef)theSocket |
{ |
if (theSocket == NULL) return 0; |
CFDataRef selfaddr; |
UInt16 selfport = 0; |
if ((selfaddr = CFSocketCopyAddress(theSocket))) |
{ |
selfport = [self addressPort:selfaddr]; |
CFRelease (selfaddr); |
} |
return selfport; |
} |
- (NSString *)addressHost:(CFDataRef)cfaddr |
{ |
if (cfaddr == NULL) return nil; |
char addrBuf[ MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) ]; |
struct sockaddr *pSockAddr = (struct sockaddr *) CFDataGetBytePtr (cfaddr); |
struct sockaddr_in *pSockAddrV4 = (struct sockaddr_in *)pSockAddr; |
struct sockaddr_in6 *pSockAddrV6 = (struct sockaddr_in6 *)pSockAddr; |
const void *pAddr = (pSockAddr->sa_family == AF_INET) ? |
(void *)(&(pSockAddrV4->sin_addr)) : |
(void *)(&(pSockAddrV6->sin6_addr)); |
const char *pStr = inet_ntop (pSockAddr->sa_family, pAddr, addrBuf, sizeof(addrBuf)); |
if (pStr == NULL) [NSException raise: NSInternalInconsistencyException |
format: @"Cannot convert address to string."]; |
return [NSString stringWithCString:pStr encoding:NSASCIIStringEncoding]; |
} |
- (UInt16)addressPort:(CFDataRef)cfaddr |
{ |
if (cfaddr == NULL) return 0; |
struct sockaddr_in *pAddr = (struct sockaddr_in *) CFDataGetBytePtr (cfaddr); |
return ntohs (pAddr->sin_port); |
} |
- (NSData *)connectedAddress |
{ |
CFSocketRef theSocket; |
if (theSocket4) |
theSocket = theSocket4; |
else |
theSocket = theSocket6; |
if (theSocket == NULL) return nil; |
CFDataRef peeraddr = CFSocketCopyPeerAddress(theSocket); |
if (peeraddr == NULL) return nil; |
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(peeraddr) length:CFDataGetLength(peeraddr)]; |
CFRelease(peeraddr); |
return result; |
#else |
return [(NSData *)NSMakeCollectable(peeraddr) autorelease]; |
#endif |
} |
- (NSData *)localAddress |
{ |
CFSocketRef theSocket; |
if (theSocket4) |
theSocket = theSocket4; |
else |
theSocket = theSocket6; |
if (theSocket == NULL) return nil; |
CFDataRef selfaddr = CFSocketCopyAddress(theSocket); |
if (selfaddr == NULL) return nil; |
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_5 |
NSData *result = [NSData dataWithBytes:CFDataGetBytePtr(selfaddr) length:CFDataGetLength(selfaddr)]; |
CFRelease(selfaddr); |
return result; |
#else |
return [(NSData *)NSMakeCollectable(selfaddr) autorelease]; |
#endif |
} |
- (BOOL)isIPv4 |
{ |
return (theSocket4 != NULL); |
} |
- (BOOL)isIPv6 |
{ |
return (theSocket6 != NULL); |
} |
- (BOOL)isSocketConnected |
{ |
if(theSocket4 != NULL) |
return CFSocketIsValid(theSocket4); |
else if(theSocket6 != NULL) |
return CFSocketIsValid(theSocket6); |
else |
return NO; |
} |
- (BOOL)areStreamsConnected |
{ |
CFStreamStatus s; |
if (theReadStream != NULL) |
{ |
s = CFReadStreamGetStatus (theReadStream); |
if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusReading || s == kCFStreamStatusError) ) |
return NO; |
} |
else return NO; |
if (theWriteStream != NULL) |
{ |
s = CFWriteStreamGetStatus (theWriteStream); |
if ( !(s == kCFStreamStatusOpen || s == kCFStreamStatusWriting || s == kCFStreamStatusError) ) |
return NO; |
} |
else return NO; |
return YES; |
} |
- (NSString *)description |
{ |
static const char *statstr[] = {"not open","opening","open","reading","writing","at end","closed","has error"}; |
CFStreamStatus rs = (theReadStream != NULL) ? CFReadStreamGetStatus(theReadStream) : 0; |
CFStreamStatus ws = (theWriteStream != NULL) ? CFWriteStreamGetStatus(theWriteStream) : 0; |
NSString *peerstr, *selfstr; |
CFDataRef peeraddr4 = NULL, peeraddr6 = NULL, selfaddr4 = NULL, selfaddr6 = NULL; |
if (theSocket4 || theSocket6) |
{ |
if (theSocket4) peeraddr4 = CFSocketCopyPeerAddress(theSocket4); |
if (theSocket6) peeraddr6 = CFSocketCopyPeerAddress(theSocket6); |
if(theSocket4 && theSocket6) |
{ |
peerstr = [NSString stringWithFormat: @"%@/%@ %u", |
[self addressHost:peeraddr4], [self addressHost:peeraddr6], [self addressPort:peeraddr4]]; |
} |
else if(theSocket4) |
{ |
peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr4], [self addressPort:peeraddr4]]; |
} |
else |
{ |
peerstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:peeraddr6], [self addressPort:peeraddr6]]; |
} |
if(peeraddr4) CFRelease(peeraddr4); |
if(peeraddr6) CFRelease(peeraddr6); |
peeraddr4 = NULL; |
peeraddr6 = NULL; |
} |
else peerstr = @"nowhere"; |
if (theSocket4 || theSocket6) |
{ |
if (theSocket4) selfaddr4 = CFSocketCopyAddress (theSocket4); |
if (theSocket6) selfaddr6 = CFSocketCopyAddress (theSocket6); |
if (theSocket4 && theSocket6) |
{ |
selfstr = [NSString stringWithFormat: @"%@/%@ %u", |
[self addressHost:selfaddr4], [self addressHost:selfaddr6], [self addressPort:selfaddr4]]; |
} |
else if (theSocket4) |
{ |
selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr4], [self addressPort:selfaddr4]]; |
} |
else |
{ |
selfstr = [NSString stringWithFormat: @"%@ %u", [self addressHost:selfaddr6], [self addressPort:selfaddr6]]; |
} |
if(selfaddr4) CFRelease(selfaddr4); |
if(selfaddr6) CFRelease(selfaddr6); |
selfaddr4 = NULL; |
selfaddr6 = NULL; |
} |
else selfstr = @"nowhere"; |
NSMutableString *ms = [[NSMutableString alloc] initWithCapacity:150]; |
[ms appendString:[NSString stringWithFormat:@"<AsyncSocket %p", self]]; |
[ms appendString:[NSString stringWithFormat:@" local %@ remote %@ ", selfstr, peerstr]]; |
unsigned readQueueCount = (unsigned)[theReadQueue count]; |
unsigned writeQueueCount = (unsigned)[theWriteQueue count]; |
[ms appendString:[NSString stringWithFormat:@"has queued %u reads %u writes, ", readQueueCount, writeQueueCount]]; |
if (theCurrentRead == nil) |
[ms appendString: @"no current read, "]; |
else |
{ |
int percentDone; |
if ([theCurrentRead->buffer length] != 0) |
percentDone = (float)theCurrentRead->bytesDone / |
(float)[theCurrentRead->buffer length] * 100.0F; |
else |
percentDone = 100.0F; |
[ms appendString: [NSString stringWithFormat:@"currently read %u bytes (%d%% done), ", |
(unsigned int)[theCurrentRead->buffer length], |
theCurrentRead->bytesDone ? percentDone : 0]]; |
} |
if (theCurrentWrite == nil) |
[ms appendString: @"no current write, "]; |
else |
{ |
int percentDone; |
if ([theCurrentWrite->buffer length] != 0) |
percentDone = (float)theCurrentWrite->bytesDone / |
(float)[theCurrentWrite->buffer length] * 100.0F; |
else |
percentDone = 100.0F; |
[ms appendString: [NSString stringWithFormat:@"currently written %u (%d%%), ", |
(unsigned int)[theCurrentWrite->buffer length], |
theCurrentWrite->bytesDone ? percentDone : 0]]; |
} |
[ms appendString:[NSString stringWithFormat:@"read stream %p %s, ", theReadStream, statstr[rs]]]; |
[ms appendString:[NSString stringWithFormat:@"write stream %p %s", theWriteStream, statstr[ws]]]; |
if(theFlags & kDisconnectAfterReads) |
{ |
if(theFlags & kDisconnectAfterWrites) |
[ms appendString: @", will disconnect after reads & writes"]; |
else |
[ms appendString: @", will disconnect after reads"]; |
} |
else if(theFlags & kDisconnectAfterWrites) |
{ |
[ms appendString: @", will disconnect after writes"]; |
} |
if (![self isConnected]) [ms appendString: @", not connected"]; |
[ms appendString:@">"]; |
return [ms autorelease]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Reading |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void)readDataToLength:(CFIndex)length withTimeout:(NSTimeInterval)timeout tag:(long)tag |
{ |
if(length == 0) return; |
if(theFlags & kForbidReadsWrites) return; |
NSMutableData *buffer = [[NSMutableData alloc] initWithLength:length]; |
AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
timeout:timeout |
tag:tag |
readAllAvailable:NO |
terminator:nil |
maxLength:length]; |
[theReadQueue addObject:packet]; |
[self scheduleDequeueRead]; |
[packet release]; |
[buffer release]; |
} |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag |
{ |
[self readDataToData:data withTimeout:timeout maxLength:-1 tag:tag]; |
} |
- (void)readDataToData:(NSData *)data withTimeout:(NSTimeInterval)timeout maxLength:(CFIndex)length tag:(long)tag |
{ |
if(data == nil || [data length] == 0) return; |
if(length >= 0 && length < [data length]) return; |
if(theFlags & kForbidReadsWrites) return; |
NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0]; |
AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
timeout:timeout |
tag:tag |
readAllAvailable:NO |
terminator:data |
maxLength:length]; |
[theReadQueue addObject:packet]; |
[self scheduleDequeueRead]; |
[packet release]; |
[buffer release]; |
} |
- (void)readDataWithTimeout:(NSTimeInterval)timeout tag:(long)tag |
{ |
if (theFlags & kForbidReadsWrites) return; |
NSMutableData *buffer = [[NSMutableData alloc] initWithLength:0]; |
AsyncReadPacket *packet = [[AsyncReadPacket alloc] initWithData:buffer |
timeout:timeout |
tag:tag |
readAllAvailable:YES |
terminator:nil |
maxLength:-1]; |
[theReadQueue addObject:packet]; |
[self scheduleDequeueRead]; |
[packet release]; |
[buffer release]; |
} |
/** |
* Puts a maybeDequeueRead on the run loop. |
* An assumption here is that selectors will be performed consecutively within their priority. |
**/ |
- (void)scheduleDequeueRead |
{ |
if((theFlags & kDequeueReadScheduled) == 0) |
{ |
theFlags |= kDequeueReadScheduled; |
[self performSelector:@selector(maybeDequeueRead) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
} |
} |
/** |
* This method starts a new read, if needed. |
* It is called when a user requests a read, |
* or when a stream opens that may have requested reads sitting in the queue, etc. |
**/ |
- (void)maybeDequeueRead |
{ |
// Unset the flag indicating a call to this method is scheduled |
theFlags &= ~kDequeueReadScheduled; |
// If we're not currently processing a read AND we have an available read stream |
if((theCurrentRead == nil) && (theReadStream != NULL)) |
{ |
if([theReadQueue count] > 0) |
{ |
// Dequeue the next object in the write queue |
theCurrentRead = [[theReadQueue objectAtIndex:0] retain]; |
[theReadQueue removeObjectAtIndex:0]; |
if([theCurrentRead isKindOfClass:[AsyncSpecialPacket class]]) |
{ |
// Attempt to start TLS |
theFlags |= kStartingReadTLS; |
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set |
[self maybeStartTLS]; |
} |
else |
{ |
// Start time-out timer |
if(theCurrentRead->timeout >= 0.0) |
{ |
theReadTimer = [NSTimer timerWithTimeInterval:theCurrentRead->timeout |
target:self |
selector:@selector(doReadTimeout:) |
userInfo:nil |
repeats:NO]; |
[self runLoopAddTimer:theReadTimer]; |
} |
// Immediately read, if possible |
[self doBytesAvailable]; |
} |
} |
else if(theFlags & kDisconnectAfterReads) |
{ |
if(theFlags & kDisconnectAfterWrites) |
{ |
if(([theWriteQueue count] == 0) && (theCurrentWrite == nil)) |
{ |
[self disconnect]; |
} |
} |
else |
{ |
[self disconnect]; |
} |
} |
} |
} |
/** |
* Call this method in doBytesAvailable instead of CFReadStreamHasBytesAvailable(). |
* This method supports pre-buffering properly as well as the kSocketHasBytesAvailable flag. |
**/ |
- (BOOL)hasBytesAvailable |
{ |
if ((theFlags & kSocketHasBytesAvailable) || ([partialReadBuffer length] > 0)) |
{ |
return YES; |
} |
else |
{ |
return CFReadStreamHasBytesAvailable(theReadStream); |
} |
} |
/** |
* Call this method in doBytesAvailable instead of CFReadStreamRead(). |
* This method support pre-buffering properly. |
**/ |
- (CFIndex)readIntoBuffer:(UInt8 *)buffer maxLength:(CFIndex)length |
{ |
if([partialReadBuffer length] > 0) |
{ |
// Determine the maximum amount of data to read |
CFIndex bytesToRead = MIN(length, [partialReadBuffer length]); |
// Copy the bytes from the buffer |
memcpy(buffer, [partialReadBuffer bytes], bytesToRead); |
// Remove the copied bytes from the buffer |
[partialReadBuffer replaceBytesInRange:NSMakeRange(0, bytesToRead) withBytes:NULL length:0]; |
return bytesToRead; |
} |
else |
{ |
// Unset the "has-bytes-available" flag |
theFlags &= ~kSocketHasBytesAvailable; |
return CFReadStreamRead(theReadStream, buffer, length); |
} |
} |
/** |
* This method is called when a new read is taken from the read queue or when new data becomes available on the stream. |
**/ |
- (void)doBytesAvailable |
{ |
// If data is available on the stream, but there is no read request, then we don't need to process the data yet. |
// Also, if there is a read request, but no read stream setup yet, we can't process any data yet. |
if((theCurrentRead != nil) && (theReadStream != NULL)) |
{ |
// Note: This method is not called if theCurrentRead is an AsyncSpecialPacket (startTLS packet) |
CFIndex totalBytesRead = 0; |
BOOL done = NO; |
BOOL socketError = NO; |
BOOL maxoutError = NO; |
while(!done && !socketError && !maxoutError && [self hasBytesAvailable]) |
{ |
BOOL didPreBuffer = NO; |
// There are 3 types of read packets: |
// |
// 1) Read a specific length of data. |
// 2) Read all available data. |
// 3) Read up to a particular terminator. |
if(theCurrentRead->readAllAvailableData == YES) |
{ |
// We're reading all available data. |
// |
// Make sure there is at least READALL_CHUNKSIZE bytes available. |
// We don't want to increase the buffer any more than this or we'll waste space. |
// With prebuffering it's possible to read in a small chunk on the first read. |
unsigned buffInc = READALL_CHUNKSIZE - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
[theCurrentRead->buffer increaseLengthBy:buffInc]; |
} |
else if(theCurrentRead->term != nil) |
{ |
// We're reading up to a terminator. |
// |
// We may only want to read a few bytes. |
// Just enough to ensure we don't go past our term or over our max limit. |
// Unless pre-buffering is enabled, in which case we may want to read in a larger chunk. |
// If we already have data pre-buffered, we obviously don't want to pre-buffer it again. |
// So in this case we'll just read as usual. |
if(([partialReadBuffer length] > 0) || !(theFlags & kEnablePreBuffering)) |
{ |
unsigned maxToRead = [theCurrentRead readLengthForTerm]; |
unsigned bufInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
[theCurrentRead->buffer increaseLengthBy:bufInc]; |
} |
else |
{ |
didPreBuffer = YES; |
unsigned maxToRead = [theCurrentRead prebufferReadLengthForTerm]; |
unsigned buffInc = maxToRead - ([theCurrentRead->buffer length] - theCurrentRead->bytesDone); |
[theCurrentRead->buffer increaseLengthBy:buffInc]; |
} |
} |
// Number of bytes to read is space left in packet buffer. |
CFIndex bytesToRead = [theCurrentRead->buffer length] - theCurrentRead->bytesDone; |
// Read data into packet buffer |
UInt8 *subBuffer = (UInt8 *)([theCurrentRead->buffer mutableBytes] + theCurrentRead->bytesDone); |
CFIndex bytesRead = [self readIntoBuffer:subBuffer maxLength:bytesToRead]; |
// Check results |
if(bytesRead < 0) |
{ |
socketError = YES; |
} |
else |
{ |
// Update total amount read for the current read |
theCurrentRead->bytesDone += bytesRead; |
// Update total amount read in this method invocation |
totalBytesRead += bytesRead; |
} |
// Is packet done? |
if(theCurrentRead->readAllAvailableData != YES) |
{ |
if(theCurrentRead->term != nil) |
{ |
if(didPreBuffer) |
{ |
// Search for the terminating sequence within the big chunk we just read. |
CFIndex overflow = [theCurrentRead searchForTermAfterPreBuffering:bytesRead]; |
if(overflow > 0) |
{ |
// Copy excess data into partialReadBuffer |
NSMutableData *buffer = theCurrentRead->buffer; |
const void *overflowBuffer = [buffer bytes] + theCurrentRead->bytesDone - overflow; |
[partialReadBuffer appendBytes:overflowBuffer length:overflow]; |
// Update the bytesDone variable. |
// Note: The completeCurrentRead method will trim the buffer for us. |
theCurrentRead->bytesDone -= overflow; |
} |
done = (overflow >= 0); |
} |
else |
{ |
// Search for the terminating sequence at the end of the buffer |
int termlen = [theCurrentRead->term length]; |
if(theCurrentRead->bytesDone >= termlen) |
{ |
const void *buf = [theCurrentRead->buffer bytes] + (theCurrentRead->bytesDone - termlen); |
const void *seq = [theCurrentRead->term bytes]; |
done = (memcmp (buf, seq, termlen) == 0); |
} |
} |
if(!done && theCurrentRead->maxLength >= 0 && theCurrentRead->bytesDone >= theCurrentRead->maxLength) |
{ |
// There's a set maxLength, and we've reached that maxLength without completing the read |
maxoutError = YES; |
} |
} |
else |
{ |
// Done when (sized) buffer is full. |
done = ([theCurrentRead->buffer length] == theCurrentRead->bytesDone); |
} |
} |
// else readAllAvailable doesn't end until all readable is read. |
} |
if(theCurrentRead->readAllAvailableData && theCurrentRead->bytesDone > 0) |
{ |
// Ran out of bytes, so the "read-all-available-data" type packet is done |
done = YES; |
} |
if(done) |
{ |
[self completeCurrentRead]; |
if (!socketError) [self scheduleDequeueRead]; |
} |
else if(totalBytesRead > 0) |
{ |
// We're not done with the readToLength or readToData yet, but we have read in some bytes |
if ([theDelegate respondsToSelector:@selector(onSocket:didReadPartialDataOfLength:tag:)]) |
{ |
[theDelegate onSocket:self didReadPartialDataOfLength:totalBytesRead tag:theCurrentRead->tag]; |
} |
} |
if(socketError) |
{ |
CFStreamError err = CFReadStreamGetError(theReadStream); |
[self closeWithError:[self errorFromCFStreamError:err]]; |
return; |
} |
if(maxoutError) |
{ |
[self closeWithError:[self getReadMaxedOutError]]; |
return; |
} |
} |
} |
// Ends current read and calls delegate. |
- (void)completeCurrentRead |
{ |
NSAssert(theCurrentRead, @"Trying to complete current read when there is no current read."); |
[theCurrentRead->buffer setLength:theCurrentRead->bytesDone]; |
if([theDelegate respondsToSelector:@selector(onSocket:didReadData:withTag:)]) |
{ |
[theDelegate onSocket:self didReadData:theCurrentRead->buffer withTag:theCurrentRead->tag]; |
} |
if (theCurrentRead != nil) [self endCurrentRead]; // Caller may have disconnected. |
} |
// Ends current read. |
- (void)endCurrentRead |
{ |
NSAssert(theCurrentRead, @"Trying to end current read when there is no current read."); |
[theReadTimer invalidate]; |
theReadTimer = nil; |
[theCurrentRead release]; |
theCurrentRead = nil; |
} |
- (void)doReadTimeout:(NSTimer *)timer |
{ |
NSTimeInterval timeoutExtension = 0.0; |
if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutReadWithTag:elapsed:bytesDone:)]) |
{ |
timeoutExtension = [theDelegate onSocket:self shouldTimeoutReadWithTag:theCurrentRead->tag |
elapsed:theCurrentRead->timeout |
bytesDone:theCurrentRead->bytesDone]; |
} |
if(timeoutExtension > 0.0) |
{ |
theCurrentRead->timeout += timeoutExtension; |
theReadTimer = [NSTimer timerWithTimeInterval:timeoutExtension |
target:self |
selector:@selector(doReadTimeout:) |
userInfo:nil |
repeats:NO]; |
[self runLoopAddTimer:theReadTimer]; |
} |
else |
{ |
// Do not call endCurrentRead here. |
// We must allow the delegate access to any partial read in the unreadData method. |
[self closeWithError:[self getReadTimeoutError]]; |
} |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Writing |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void)writeData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag |
{ |
if (data == nil || [data length] == 0) return; |
if (theFlags & kForbidReadsWrites) return; |
AsyncWritePacket *packet = [[AsyncWritePacket alloc] initWithData:data timeout:timeout tag:tag]; |
[theWriteQueue addObject:packet]; |
[self scheduleDequeueWrite]; |
[packet release]; |
} |
- (void)scheduleDequeueWrite |
{ |
if((theFlags & kDequeueWriteScheduled) == 0) |
{ |
theFlags |= kDequeueWriteScheduled; |
[self performSelector:@selector(maybeDequeueWrite) withObject:nil afterDelay:0 inModes:theRunLoopModes]; |
} |
} |
/** |
* Conditionally starts a new write. |
* |
* IF there is not another write in process |
* AND there is a write queued |
* AND we have a write stream available |
* |
* This method also handles auto-disconnect post read/write completion. |
**/ |
- (void)maybeDequeueWrite |
{ |
// Unset the flag indicating a call to this method is scheduled |
theFlags &= ~kDequeueWriteScheduled; |
// If we're not currently processing a write AND we have an available write stream |
if((theCurrentWrite == nil) && (theWriteStream != NULL)) |
{ |
if([theWriteQueue count] > 0) |
{ |
// Dequeue the next object in the write queue |
theCurrentWrite = [[theWriteQueue objectAtIndex:0] retain]; |
[theWriteQueue removeObjectAtIndex:0]; |
if([theCurrentWrite isKindOfClass:[AsyncSpecialPacket class]]) |
{ |
// Attempt to start TLS |
theFlags |= kStartingWriteTLS; |
// This method won't do anything unless both kStartingReadTLS and kStartingWriteTLS are both set |
[self maybeStartTLS]; |
} |
else |
{ |
// Start time-out timer |
if(theCurrentWrite->timeout >= 0.0) |
{ |
theWriteTimer = [NSTimer timerWithTimeInterval:theCurrentWrite->timeout |
target:self |
selector:@selector(doWriteTimeout:) |
userInfo:nil |
repeats:NO]; |
[self runLoopAddTimer:theWriteTimer]; |
} |
// Immediately write, if possible |
[self doSendBytes]; |
} |
} |
else if(theFlags & kDisconnectAfterWrites) |
{ |
if(theFlags & kDisconnectAfterReads) |
{ |
if(([theReadQueue count] == 0) && (theCurrentRead == nil)) |
{ |
[self disconnect]; |
} |
} |
else |
{ |
[self disconnect]; |
} |
} |
} |
} |
/** |
* Call this method in doSendBytes instead of CFWriteStreamCanAcceptBytes(). |
* This method supports the kSocketCanAcceptBytes flag. |
**/ |
- (BOOL)canAcceptBytes |
{ |
if (theFlags & kSocketCanAcceptBytes) |
{ |
return YES; |
} |
else |
{ |
return CFWriteStreamCanAcceptBytes(theWriteStream); |
} |
} |
- (void)doSendBytes |
{ |
if((theCurrentWrite != nil) && (theWriteStream != NULL)) |
{ |
// Note: This method is not called if theCurrentWrite is an AsyncSpecialPacket (startTLS packet) |
CFIndex totalBytesWritten = 0; |
BOOL done = NO; |
BOOL error = NO; |
while (!done && !error && [self canAcceptBytes]) |
{ |
// Figure out what to write. |
CFIndex bytesRemaining = [theCurrentWrite->buffer length] - theCurrentWrite->bytesDone; |
CFIndex bytesToWrite = (bytesRemaining < WRITE_CHUNKSIZE) ? bytesRemaining : WRITE_CHUNKSIZE; |
UInt8 *writestart = (UInt8 *)([theCurrentWrite->buffer bytes] + theCurrentWrite->bytesDone); |
// Write. |
CFIndex bytesWritten = CFWriteStreamWrite(theWriteStream, writestart, bytesToWrite); |
// Unset the "can accept bytes" flag |
theFlags &= ~kSocketCanAcceptBytes; |
// Check results |
if (bytesWritten < 0) |
{ |
error = YES; |
} |
else |
{ |
// Update total amount read for the current write |
theCurrentWrite->bytesDone += bytesWritten; |
// Update total amount written in this method invocation |
totalBytesWritten += bytesWritten; |
// Is packet done? |
done = ([theCurrentWrite->buffer length] == theCurrentWrite->bytesDone); |
} |
} |
if(done) |
{ |
[self completeCurrentWrite]; |
[self scheduleDequeueWrite]; |
} |
else if(error) |
{ |
CFStreamError err = CFWriteStreamGetError(theWriteStream); |
[self closeWithError:[self errorFromCFStreamError:err]]; |
return; |
} |
else |
{ |
// We're not done with the entire write, but we have written some bytes |
if ([theDelegate respondsToSelector:@selector(onSocket:didWritePartialDataOfLength:tag:)]) |
{ |
[theDelegate onSocket:self didWritePartialDataOfLength:totalBytesWritten tag:theCurrentWrite->tag]; |
} |
} |
} |
} |
// Ends current write and calls delegate. |
- (void)completeCurrentWrite |
{ |
NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); |
if ([theDelegate respondsToSelector:@selector(onSocket:didWriteDataWithTag:)]) |
{ |
[theDelegate onSocket:self didWriteDataWithTag:theCurrentWrite->tag]; |
} |
if (theCurrentWrite != nil) [self endCurrentWrite]; // Caller may have disconnected. |
} |
// Ends current write. |
- (void)endCurrentWrite |
{ |
NSAssert(theCurrentWrite, @"Trying to complete current write when there is no current write."); |
[theWriteTimer invalidate]; |
theWriteTimer = nil; |
[theCurrentWrite release]; |
theCurrentWrite = nil; |
} |
- (void)doWriteTimeout:(NSTimer *)timer |
{ |
NSTimeInterval timeoutExtension = 0.0; |
if([theDelegate respondsToSelector:@selector(onSocket:shouldTimeoutWriteWithTag:elapsed:bytesDone:)]) |
{ |
timeoutExtension = [theDelegate onSocket:self shouldTimeoutWriteWithTag:theCurrentWrite->tag |
elapsed:theCurrentWrite->timeout |
bytesDone:theCurrentWrite->bytesDone]; |
} |
if(timeoutExtension > 0.0) |
{ |
theCurrentWrite->timeout += timeoutExtension; |
theWriteTimer = [NSTimer timerWithTimeInterval:timeoutExtension |
target:self |
selector:@selector(doWriteTimeout:) |
userInfo:nil |
repeats:NO]; |
[self runLoopAddTimer:theWriteTimer]; |
} |
else |
{ |
[self closeWithError:[self getWriteTimeoutError]]; |
} |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Security |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void)startTLS:(NSDictionary *)tlsSettings |
{ |
if(tlsSettings == nil) |
{ |
// Passing nil/NULL to CFReadStreamSetProperty will appear to work the same as passing an empty dictionary, |
// but causes problems if we later try to fetch the remote host's certificate. |
// |
// To be exact, it causes the following to return NULL instead of the normal result: |
// CFReadStreamCopyProperty(readStream, kCFStreamPropertySSLPeerCertificates) |
// |
// So we use an empty dictionary instead, which works perfectly. |
tlsSettings = [NSDictionary dictionary]; |
} |
AsyncSpecialPacket *packet = [[AsyncSpecialPacket alloc] initWithTLSSettings:tlsSettings]; |
[theReadQueue addObject:packet]; |
[self scheduleDequeueRead]; |
[theWriteQueue addObject:packet]; |
[self scheduleDequeueWrite]; |
[packet release]; |
} |
- (void)maybeStartTLS |
{ |
// We can't start TLS until: |
// - All queued reads prior to the user calling StartTLS are complete |
// - All queued writes prior to the user calling StartTLS are complete |
// |
// We'll know these conditions are met when both kStartingReadTLS and kStartingWriteTLS are set |
if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) |
{ |
AsyncSpecialPacket *tlsPacket = (AsyncSpecialPacket *)theCurrentRead; |
BOOL didStartOnReadStream = CFReadStreamSetProperty(theReadStream, kCFStreamPropertySSLSettings, |
(CFDictionaryRef)tlsPacket->tlsSettings); |
BOOL didStartOnWriteStream = CFWriteStreamSetProperty(theWriteStream, kCFStreamPropertySSLSettings, |
(CFDictionaryRef)tlsPacket->tlsSettings); |
if(!didStartOnReadStream || !didStartOnWriteStream) |
{ |
[self closeWithError:[self getSocketError]]; |
} |
} |
} |
- (void)onTLSHandshakeSuccessful |
{ |
if((theFlags & kStartingReadTLS) && (theFlags & kStartingWriteTLS)) |
{ |
theFlags &= ~kStartingReadTLS; |
theFlags &= ~kStartingWriteTLS; |
if([theDelegate respondsToSelector:@selector(onSocketDidSecure:)]) |
{ |
[theDelegate onSocketDidSecure:self]; |
} |
[self endCurrentRead]; |
[self endCurrentWrite]; |
[self scheduleDequeueRead]; |
[self scheduleDequeueWrite]; |
} |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark CF Callbacks |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void)doCFSocketCallback:(CFSocketCallBackType)type |
forSocket:(CFSocketRef)sock |
withAddress:(NSData *)address |
withData:(const void *)pData |
{ |
NSParameterAssert ((sock == theSocket4) || (sock == theSocket6)); |
switch (type) |
{ |
case kCFSocketConnectCallBack: |
// The data argument is either NULL or a pointer to an SInt32 error code, if the connect failed. |
if(pData) |
[self doSocketOpen:sock withCFSocketError:kCFSocketError]; |
else |
[self doSocketOpen:sock withCFSocketError:kCFSocketSuccess]; |
break; |
case kCFSocketAcceptCallBack: |
[self doAcceptWithSocket: *((CFSocketNativeHandle *)pData)]; |
break; |
default: |
NSLog (@"AsyncSocket %p received unexpected CFSocketCallBackType %d.", self, type); |
break; |
} |
} |
- (void)doCFReadStreamCallback:(CFStreamEventType)type forStream:(CFReadStreamRef)stream |
{ |
NSParameterAssert(theReadStream != NULL); |
CFStreamError err; |
switch (type) |
{ |
case kCFStreamEventOpenCompleted: |
theFlags |= kDidCompleteOpenForRead; |
[self doStreamOpen]; |
break; |
case kCFStreamEventHasBytesAvailable: |
if(theFlags & kStartingReadTLS) { |
[self onTLSHandshakeSuccessful]; |
} |
else { |
theFlags |= kSocketHasBytesAvailable; |
[self doBytesAvailable]; |
} |
break; |
case kCFStreamEventErrorOccurred: |
case kCFStreamEventEndEncountered: |
err = CFReadStreamGetError (theReadStream); |
[self closeWithError: [self errorFromCFStreamError:err]]; |
break; |
default: |
NSLog (@"AsyncSocket %p received unexpected CFReadStream callback, CFStreamEventType %d.", self, type); |
} |
} |
- (void)doCFWriteStreamCallback:(CFStreamEventType)type forStream:(CFWriteStreamRef)stream |
{ |
NSParameterAssert(theWriteStream != NULL); |
CFStreamError err; |
switch (type) |
{ |
case kCFStreamEventOpenCompleted: |
theFlags |= kDidCompleteOpenForWrite; |
[self doStreamOpen]; |
break; |
case kCFStreamEventCanAcceptBytes: |
if(theFlags & kStartingWriteTLS) { |
[self onTLSHandshakeSuccessful]; |
} |
else { |
theFlags |= kSocketCanAcceptBytes; |
[self doSendBytes]; |
} |
break; |
case kCFStreamEventErrorOccurred: |
case kCFStreamEventEndEncountered: |
err = CFWriteStreamGetError (theWriteStream); |
[self closeWithError: [self errorFromCFStreamError:err]]; |
break; |
default: |
NSLog (@"AsyncSocket %p received unexpected CFWriteStream callback, CFStreamEventType %d.", self, type); |
} |
} |
/** |
* This is the callback we setup for CFSocket. |
* This method does nothing but forward the call to it's Objective-C counterpart |
**/ |
static void MyCFSocketCallback (CFSocketRef sref, CFSocketCallBackType type, CFDataRef address, const void *pData, void *pInfo) |
{ |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
[theSocket doCFSocketCallback:type forSocket:sref withAddress:(NSData *)address withData:pData]; |
[pool release]; |
} |
/** |
* This is the callback we setup for CFReadStream. |
* This method does nothing but forward the call to it's Objective-C counterpart |
**/ |
static void MyCFReadStreamCallback (CFReadStreamRef stream, CFStreamEventType type, void *pInfo) |
{ |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
[theSocket doCFReadStreamCallback:type forStream:stream]; |
[pool release]; |
} |
/** |
* This is the callback we setup for CFWriteStream. |
* This method does nothing but forward the call to it's Objective-C counterpart |
**/ |
static void MyCFWriteStreamCallback (CFWriteStreamRef stream, CFStreamEventType type, void *pInfo) |
{ |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
AsyncSocket *theSocket = [[(AsyncSocket *)pInfo retain] autorelease]; |
[theSocket doCFWriteStreamCallback:type forStream:stream]; |
[pool release]; |
} |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark Class Methods |
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// |
// Return line separators. |
+ (NSData *)CRLFData |
{ |
return [NSData dataWithBytes:"\x0D\x0A" length:2]; |
} |
+ (NSData *)CRData |
{ |
return [NSData dataWithBytes:"\x0D" length:1]; |
} |
+ (NSData *)LFData |
{ |
return [NSData dataWithBytes:"\x0A" length:1]; |
} |
+ (NSData *)ZeroData |
{ |
return [NSData dataWithBytes:"" length:1]; |
} |
@end |
/iKopter/trunk/Classes/Communication/MKConnection.h |
---|
0,0 → 1,48 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
@protocol MKConnectionDelegate < NSObject > |
@optional |
- (void) didConnectTo:(NSString *)hostOrDevice; |
- (void) willDisconnectWithError:(NSError *)err; |
- (void) didDisconnect; |
- (void) didReadMkData:(NSData *)data; |
@end |
@protocol MKConnection |
@property(assign) id<MKConnectionDelegate> delegate; |
- (id) initWithDelegate:(id<MKConnectionDelegate>)delegate; |
- (BOOL) connectTo:(NSString *)hostOrDevice error:(NSError **)err; |
- (BOOL) isConnected; |
- (void) disconnect; |
- (void) writeMkData:(NSData *)data; |
@end |
/iKopter/trunk/Classes/Communication/MKConnectionController.h |
---|
0,0 → 1,89 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKConnection.h" |
#import "MKDatatypes.h" |
extern NSString * const MKConnectedNotification; |
extern NSString * const MKDisconnectedNotification; |
extern NSString * const MKDisconnectErrorNotification; |
extern NSString * const MKVersionNotification; |
extern NSString * const MKDebugDataNotification; |
extern NSString * const MKDebugLabelNotification; |
extern NSString * const MKLcdMenuNotification; |
extern NSString * const MKLcdNotification; |
extern NSString * const MKReadSettingNotification; |
extern NSString * const MKWriteSettingNotification; |
extern NSString * const MKChangeSettingNotification; |
extern NSString * const MKChannelValuesNotification; |
extern NSString * const MKReadMixerNotification; |
extern NSString * const MKWriteMixerNotification; |
extern NSString * const MKOsdNotification; |
@class MKHost; |
@interface MKConnectionController : NSObject<MKConnectionDelegate> { |
NSObject<MKConnection> * inputController; |
NSString * hostOrDevice; |
BOOL didPostConnectNotification; |
MKAddress primaryDevice; |
MKAddress currentDevice; |
NSString * shortVersions[3]; |
NSString * longVersions[3]; |
} |
@property(readonly) MKAddress primaryDevice; |
@property(assign,readonly) MKAddress currentDevice; |
+ (MKConnectionController *) sharedMKConnectionController; |
- (void) start:(MKHost*)host; |
- (void) stop; |
- (BOOL) isRunning; |
- (void) sendRequest:(NSData *)data; |
- (BOOL) hasNaviCtrl; |
- (void) activateNaviCtrl; |
- (void) activateFlightCtrl; |
- (void) activateMK3MAG; |
- (void) activateMKGPS; |
- (NSString *) shortVersionForAddress:(MKAddress)theAddress; |
- (NSString *) longVersionForAddress:(MKAddress)theAddress; |
@end |
/iKopter/trunk/Classes/Communication/MKConnectionController.m |
---|
0,0 → 1,414 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "SynthesizeSingleton.h" |
#import "MKConnectionController.h" |
#import "NSData+MKCommandDecode.h" |
#import "NSData+MKCommandEncode.h" |
#import "NSData+MKPayloadDecode.h" |
#import "MKDataConstants.h" |
#import "MKHost.h" |
#import "MKIpConnection.h" |
// /////////////////////////////////////////////////////////////////////////////// |
NSString * const MKConnectedNotification = @"MKConnectedNotification"; |
NSString * const MKDisconnectedNotification = @"MKDisconnectedNotification"; |
NSString * const MKDisconnectErrorNotification = @"MKDisconnectErrorNotification"; |
NSString * const MKVersionNotification = @"MKVersionNotification"; |
NSString * const MKDebugDataNotification = @"MKDebugDataNotification"; |
NSString * const MKDebugLabelNotification = @"MKDebugLabelNotification"; |
NSString * const MKLcdMenuNotification = @"MKLcdMenuNotification"; |
NSString * const MKLcdNotification = @"MKLcdNotification"; |
NSString * const MKReadSettingNotification = @"MKReadSettingNotification"; |
NSString * const MKWriteSettingNotification = @"MKWriteSettingNotification"; |
NSString * const MKChangeSettingNotification = @"MKChangeSettingNotification"; |
NSString * const MKChannelValuesNotification = @"MKChannelValuesNotification"; |
NSString * const MKReadMixerNotification = @"MKReadMixerNotification"; |
NSString * const MKWriteMixerNotification = @"MKWriteMixerNotification"; |
NSString * const MKOsdNotification = @"MKOsdNotification"; |
// /////////////////////////////////////////////////////////////////////////////// |
@implementation MKConnectionController |
// ------------------------------------------------------------------------------- |
//@synthesize hostOrDevice; |
//@synthesize inputController; |
@synthesize primaryDevice; |
@synthesize currentDevice; |
-(void) setCurrentDevice:(MKAddress)theAddress { |
// if(primaryDevice==MKAddressNC) { |
if(theAddress != currentDevice) { |
currentDevice=theAddress; |
if(currentDevice==MKAddressNC) |
{ |
uint8_t bytes[5]; |
bytes[0] = 0x1B; |
bytes[1] = 0x1B; |
bytes[2] = 0x55; |
bytes[3] = 0xAA; |
bytes[4] = 0x00; |
bytes[5] = '\r'; |
NSData * data = [NSData dataWithBytes:&bytes length:6]; |
[self sendRequest:data]; |
} |
else { |
uint8_t byte=0; |
switch (currentDevice) { |
case MKAddressFC: |
byte=0; |
break; |
case MKAddressMK3MAg: |
byte=1; |
break; |
} |
NSData * data = [NSData dataWithCommand:MKCommandRedirectRequest |
forAddress:MKAddressNC |
payloadForByte:byte]; |
[self sendRequest:data]; |
} |
} |
// } |
} |
SYNTHESIZE_SINGLETON_FOR_CLASS(MKConnectionController); |
// ------------------------------------------------------------------------------- |
- (void) dealloc { |
[hostOrDevice release]; |
[inputController release]; |
[shortVersions[MKAddressFC] release]; |
[shortVersions[MKAddressNC] release]; |
[shortVersions[MKAddressMK3MAg] release]; |
[longVersions[MKAddressMK3MAg] release]; |
[longVersions[MKAddressNC] release]; |
[longVersions[MKAddressMK3MAg] release]; |
[super dealloc]; |
} |
// ------------------------------------------------------------------------------- |
- (void) start:(MKHost*)host { |
if (![inputController isConnected]) { |
Class nsclass = NSClassFromString(host.connectionClass); |
if (!nsclass) { |
nsclass = [MKIpConnection class]; |
} |
[inputController release]; |
inputController = [[nsclass alloc] initWithDelegate:self]; |
[hostOrDevice release]; |
hostOrDevice = [NSString stringWithFormat:@"%@:%d",host.address,host.port]; |
[hostOrDevice retain]; |
didPostConnectNotification = NO; |
currentDevice=MKAddressAll; |
primaryDevice=MKAddressAll; |
NSError * err = nil; |
if (![inputController connectTo:hostOrDevice error:&err]) { |
NSLog(@"Error: %@", err); |
} |
} |
} |
- (void) stop { |
if ([inputController isConnected]) { |
DLog(@"disconnect"); |
[inputController disconnect]; |
} |
// self.inputController=nil; |
} |
- (BOOL) isRunning; |
{ |
return [inputController isConnected]; |
} |
- (void) sendRequest:(NSData *)data; |
{ |
DLog(@"%@",data); |
NSString * msg = [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease]; |
DLog(@"%@", msg); |
[inputController writeMkData:data]; |
} |
- (NSString *) shortVersionForAddress:(MKAddress)theAddress; |
{ |
if (theAddress <= MKAddressAll || theAddress > MKAddressMK3MAg) |
return nil; |
return shortVersions[theAddress]; |
} |
- (NSString *) longVersionForAddress:(MKAddress)theAddress; |
{ |
if (theAddress <= MKAddressAll || theAddress > MKAddressMK3MAg) |
return nil; |
return longVersions[theAddress]; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
- (BOOL) hasNaviCtrl { |
return primaryDevice==MKAddressNC; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void) activateNaviCtrl { |
if(primaryDevice!=MKAddressAll && ![self hasNaviCtrl]) |
return; |
uint8_t bytes[5]; |
bytes[0] = 0x1B; |
bytes[1] = 0x1B; |
bytes[2] = 0x55; |
bytes[3] = 0xAA; |
bytes[4] = 0x00; |
bytes[5] = '\r'; |
NSData * data = [NSData dataWithBytes:&bytes length:6]; |
[self sendRequest:data]; |
currentDevice=MKAddressNC; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void) activateFlightCtrl { |
if(![self hasNaviCtrl]) |
return; |
uint8_t byte=0; |
NSData * data = [NSData dataWithCommand:MKCommandRedirectRequest |
forAddress:MKAddressNC |
payloadForByte:byte]; |
[self sendRequest:data]; |
currentDevice=MKAddressFC; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void) activateMK3MAG { |
if(![self hasNaviCtrl]) |
return; |
uint8_t byte=1; |
NSData * data = [NSData dataWithCommand:MKCommandRedirectRequest |
forAddress:MKAddressNC |
payloadForByte:byte]; |
[self sendRequest:data]; |
currentDevice=MKAddressMK3MAg; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
- (void) activateMKGPS { |
if(![self hasNaviCtrl]) |
return; |
uint8_t byte=3; |
NSData * data = [NSData dataWithCommand:MKCommandRedirectRequest |
forAddress:MKAddressNC |
payloadForByte:byte]; |
[self sendRequest:data]; |
currentDevice=MKAddressMKGPS; |
} |
/////////////////////////////////////////////////////////////////////////////////////////////////// |
#pragma mark - |
#pragma mark MKInputDelegate |
- (void) requestPrimaryDeviceVersion { |
NSData * data = [NSData dataWithCommand:MKCommandVersionRequest |
forAddress:MKAddressAll |
payloadWithBytes:NULL |
length:0]; |
[self sendRequest:data]; |
} |
- (void) didConnectTo:(NSString *)hostOrDevice { |
[self activateNaviCtrl]; |
[self performSelector:@selector(requestPrimaryDeviceVersion) withObject:self afterDelay:0.1]; |
} |
- (void) willDisconnectWithError:(NSError *)err { |
NSDictionary * d = [NSDictionary dictionaryWithObjectsAndKeys:err, @"error", nil]; |
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; |
[nc postNotificationName:MKDisconnectErrorNotification object:self userInfo:d]; |
} |
- (void) didDisconnect { |
NSDictionary * d = nil; |
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; |
[nc postNotificationName:MKDisconnectedNotification object:self userInfo:d]; |
} |
- (void) setVersionsFrom:(NSDictionary *)d forAddress:(MKAddress)address { |
if (address != MKAddressAll) { |
[shortVersions[address] release]; |
shortVersions[address] = [[d objectForKey:kMKDataKeyVersion] retain]; |
[longVersions[address] release]; |
longVersions[address] = [[d objectForKey:kMKDataKeyVersionShort] retain]; |
} |
} |
- (void) didReadMkData:(NSData *)data { |
NSData * strData = [data subdataWithRange:NSMakeRange(1, [data length] - 1)]; |
NSString * msg = [[[NSString alloc] initWithData:strData encoding:NSASCIIStringEncoding] autorelease]; |
DLog(@">>%@<<", msg); |
if ([strData isCrcOk]) { |
// DLog(@"Data length %d",[strData length]); |
NSData * payload = [strData payload]; |
MKAddress address = [strData address]; |
NSDictionary * d = nil; |
NSString * n = nil; |
switch ([strData command]) { |
case MKCommandLcdMenuResponse: |
n = MKLcdMenuNotification; |
d = [payload decodeLcdMenuResponseForAddress:address]; |
break; |
case MKCommandLcdResponse: |
n = MKLcdNotification; |
d = [payload decodeLcdResponseForAddress:address]; |
break; |
case MKCommandDebugLabelResponse: |
n = MKDebugLabelNotification; |
d = [payload decodeAnalogLabelResponseForAddress:address]; |
break; |
case MKCommandDebugValueResponse: |
n = MKDebugDataNotification; |
d = [payload decodeDebugDataResponseForAddress:address]; |
break; |
case MKCommandChannelsValueResponse: |
n = MKChannelValuesNotification; |
d = [payload decodeChannelsDataResponse]; |
break; |
case MKCommandReadSettingsResponse: |
n = MKReadSettingNotification; |
d = [payload decodeReadSettingResponse]; |
break; |
case MKCommandWriteSettingsResponse: |
n = MKWriteSettingNotification; |
d = [payload decodeWriteSettingResponse]; |
break; |
case MKCommandMixerReadResponse: |
n = MKReadMixerNotification; |
d = [payload decodeMixerReadResponse]; |
break; |
case MKCommandMixerWriteResponse: |
n = MKWriteMixerNotification; |
d = [payload decodeMixerWriteResponse]; |
break; |
case MKCommandChangeSettingsResponse: |
n = MKChangeSettingNotification; |
d = [payload decodeChangeSettingResponse]; |
break; |
case MKCommandOsdResponse: |
n = MKOsdNotification; |
d = [payload decodeOsdResponse]; |
break; |
case MKCommandVersionResponse: |
n = MKVersionNotification; |
d = [payload decodeVersionResponseForAddress:address]; |
[self setVersionsFrom:d forAddress:address]; |
if (!didPostConnectNotification) { |
currentDevice=address; |
primaryDevice=address; |
DLog(@"Connected to primaryDevice %d, currentDevice %d",primaryDevice,currentDevice); |
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; |
[nc postNotificationName:MKConnectedNotification object:self userInfo:nil]; |
} |
break; |
default: |
break; |
} |
if (d) |
DLog(@"(%d) %@", [d retainCount],d ); |
NSNotificationCenter * nc = [NSNotificationCenter defaultCenter]; |
[nc postNotificationName:n object:self userInfo:d]; |
// if (d) |
// DLog(@"(%d)", [d retainCount] ); |
} else { |
NSString * msg = [[[NSString alloc] initWithData:strData encoding:NSASCIIStringEncoding] autorelease]; |
DLog(@"%@", msg); |
} |
} |
@end |
/iKopter/trunk/Classes/Communication/MKDataConstants.h |
---|
0,0 → 1,278 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#define kHostnameKey @"hostname" |
#define kHostportKey @"hostport" |
#define kUseDummyConnKey @"usedummy" |
#define kUseConnClassKey @"useconnclass" |
#define kMKDataKeyVersion @"version" |
#define kMKDataKeyVersionShort @"versionShort" |
#define kMKDataKeyMenuItem @"menuItem" |
#define kMKDataKeyMaxMenuItem @"maxMenuItem" |
#define kMKDataKeyMenuRows @"menuRows" |
#define kMKDataKeyDebugData @"debugData" |
#define kMKDataKeyLabel @"label" |
#define kMKDataKeyIndex @"index" |
#define kMKDataKeyChannels @"channels" |
#define kMKDataKeyResult @"result" |
#define kMKDataKeyRawValue @"rawvalue" |
#define kMKDataKeyAddress @"address" |
// unsigned char Kanalbelegung[12]; |
#define kKeyKanalbelegung00 @"Kanalbelegung00" |
#define kKeyKanalbelegung01 @"Kanalbelegung01" |
#define kKeyKanalbelegung02 @"Kanalbelegung02" |
#define kKeyKanalbelegung03 @"Kanalbelegung03" |
#define kKeyKanalbelegung04 @"Kanalbelegung04" |
#define kKeyKanalbelegung05 @"Kanalbelegung05" |
#define kKeyKanalbelegung06 @"Kanalbelegung06" |
#define kKeyKanalbelegung07 @"Kanalbelegung07" |
#define kKeyKanalbelegung08 @"Kanalbelegung08" |
#define kKeyKanalbelegung09 @"Kanalbelegung09" |
#define kKeyKanalbelegung10 @"Kanalbelegung10" |
#define kKeyKanalbelegung11 @"Kanalbelegung11" |
#define kKeyKanalbelegung12 @"Kanalbelegung12" |
#define kKeyGlobalConfig @"GlobalConfig" |
// unsigned char GlobalConfig; |
// #define CFG_HOEHENREGELUNG 0x01 |
// #define CFG_HOEHEN_SCHALTER 0x02 |
// #define CFG_HEADING_HOLD 0x04 |
// #define CFG_KOMPASS_AKTIV 0x08 |
// #define CFG_KOMPASS_FIX 0x10 |
// #define CFG_GPS_AKTIV 0x20 |
// #define CFG_ACHSENKOPPLUNG_AKTIV 0x40 |
// #define CFG_DREHRATEN_BEGRENZER 0x80 |
#define kKeyGlobalConfig_HOEHENREGELUNG @"GlobalConfig_HOEHENREGELUNG" |
#define kKeyGlobalConfig_HOEHEN_SCHALTER @"GlobalConfig_HOEHEN_SCHALTER" |
#define kKeyGlobalConfig_HEADING_HOLD @"GlobalConfig_HEADING_HOLD" |
#define kKeyGlobalConfig_KOMPASS_AKTIV @"GlobalConfig_KOMPASS_AKTIV" |
#define kKeyGlobalConfig_KOMPASS_FIX @"GlobalConfig_KOMPASS_FIX" |
#define kKeyGlobalConfig_GPS_AKTIV @"GlobalConfig_GPS_AKTIV" |
#define kKeyGlobalConfig_ACHSENKOPPLUNG_AKTIV @"GlobalConfig_ACHSENKOPPLUNG_AKTIV" |
#define kKeyGlobalConfig_DREHRATEN_BEGRENZER @"GlobalConfig_DREHRATEN_BEGRENZER" |
// unsigned char Hoehe_MinGas; // Wert : 0-100 |
#define kKeyHoehe_MinGas @"Hoehe_MinGas" |
// unsigned char Luftdruck_D; // Wert : 0-250 |
#define kKeyLuftdruck_D @"Luftdruck_D" |
// unsigned char MaxHoehe; // Wert : 0-32 |
#define kKeyMaxHoehe @"MaxHoehe" |
// unsigned char Hoehe_P; // Wert : 0-32 |
#define kKeyHoehe_P @"Hoehe_P" |
// unsigned char Hoehe_Verstaerkung; // Wert : 0-50 |
#define kKeyHoehe_Verstaerkung @"Hoehe_Verstaerkung" |
// unsigned char Hoehe_ACC_Wirkung; // Wert : 0-250 |
#define kKeyHoehe_ACC_Wirkung @"Hoehe_ACC_Wirkung" |
// unsigned char Hoehe_HoverBand; // Wert : 0-250 |
#define kKeyHoehe_HoverBand @"Hoehe_HoverBand" |
// unsigned char Hoehe_GPS_Z; // Wert : 0-250 |
#define kKeyHoehe_GPS_Z @"Hoehe_GPS_Z" |
// unsigned char Hoehe_StickNeutralPoint;// Wert : 0-250 |
#define kKeyHoehe_StickNeutralPoint @"Hoehe_StickNeutralPoint" |
// unsigned char Stick_P; // Wert : 1-6 |
#define kKeyStick_P @"Stick_P" |
// unsigned char Stick_D; // Wert : 0-64 |
#define kKeyStick_D @"Stick_D" |
// unsigned char Gier_P; // Wert : 1-20 |
#define kKeyGier_P @"Gier_P" |
// unsigned char Gas_Min; // Wert : 0-32 |
#define kKeyGas_Min @"Gas_Min" |
// unsigned char Gas_Max; // Wert : 33-250 |
#define kKeyGas_Max @"Gas_Max" |
// unsigned char GyroAccFaktor; // Wert : 1-64 |
#define kKeyGyroAccFaktor @"GyroAccFaktor" |
// unsigned char KompassWirkung; // Wert : 0-32 |
#define kKeyKompassWirkung @"KompassWirkung" |
// unsigned char Gyro_P; // Wert : 10-250 |
#define kKeyGyro_P @"Gyro_P" |
// unsigned char Gyro_I; // Wert : 0-250 |
#define kKeyGyro_I @"Gyro_I" |
// unsigned char Gyro_D; // Wert : 0-250 |
#define kKeyGyro_D @"Gyro_D" |
// unsigned char Gyro_Gier_P; // Wert : 10-250 |
#define kKeyGyro_Gier_P @"Gyro_Gier_P" |
// unsigned char Gyro_Gier_I; // Wert : 0-250 |
#define kKeyGyro_Gier_I @"Gyro_Gier_I" |
// unsigned char UnterspannungsWarnung; // Wert : 0-250 |
#define kKeyUnterspannungsWarnung @"UnterspannungsWarnung" |
// unsigned char NotGas; // Wert : 0-250 //Gaswert bei Empfangsverlust |
#define kKeyNotGas @"NotGas" |
// unsigned char NotGasZeit; // Wert : 0-250 // Zeitbis auf NotGas geschaltet wird, wg. Rx-Problemen |
#define kKeyNotGasZeit @"NotGasZeit" |
// unsigned char Receiver; // 0= Summensignal, 1= Spektrum, 2 =Jeti, 3=ACT DSL, 4=ACT S3D |
#define kKeyReceiver @"Receiver" |
// unsigned char I_Faktor; // Wert : 0-250 |
#define kKeyI_Faktor @"I_Faktor" |
// unsigned char UserParam1; // Wert : 0-250 |
#define kKeyUserParam1 @"UserParam1" |
// unsigned char UserParam2; // Wert : 0-250 |
#define kKeyUserParam2 @"UserParam2" |
// unsigned char UserParam3; // Wert : 0-250 |
#define kKeyUserParam3 @"UserParam3" |
// unsigned char UserParam4; // Wert : 0-250 |
#define kKeyUserParam4 @"UserParam4" |
// unsigned char ServoNickControl; // Wert : 0-250 // Stellung des Servos |
#define kKeyServoNickControl @"ServoNickControl" |
// unsigned char ServoNickComp; // Wert : 0-250 // Einfluss Gyro/Servo |
#define kKeyServoNickComp @"ServoNickComp" |
// unsigned char ServoNickMin; // Wert : 0-250 // Anschlag |
#define kKeyServoNickMin @"ServoNickMin" |
// unsigned char ServoNickMax; // Wert : 0-250 // Anschlag |
#define kKeyServoNickMax @"ServoNickMax" |
// //--- Seit V0.75 |
// unsigned char ServoRollControl; // Wert : 0-250 // Stellung des Servos |
#define kKeyServoRollControl @"ServoRollControl" |
// unsigned char ServoRollComp; // Wert : 0-250 |
#define kKeyServoRollComp @"ServoRollComp" |
// unsigned char ServoRollMin; // Wert : 0-250 |
#define kKeyServoRollMin @"ServoRollMin" |
// unsigned char ServoRollMax; // Wert : 0-250 |
#define kKeyServoRollMax @"ServoRollMax" |
// //--- |
// unsigned char ServoNickRefresh; // Speed of the Servo |
#define kKeyServoNickRefresh @"ServoNickRefresh" |
// unsigned char Servo3; // Value or mapping of the Servo Output |
#define kKeyServo3 @"Servo3" |
// unsigned char Servo4; // Value or mapping of the Servo Output |
#define kKeyServo4 @"Servo4" |
// unsigned char Servo5; // Value or mapping of the Servo Output |
#define kKeyServo5 @"Servo5" |
// unsigned char LoopGasLimit; // Wert: 0-250 max. Gas während Looping |
#define kKeyLoopGasLimit @"LoopGasLimit" |
// unsigned char LoopThreshold; // Wert: 0-250 Schwelle für Stickausschlag |
#define kKeyLoopThreshold @"LoopThreshold" |
// unsigned char LoopHysterese; // Wert: 0-250 Hysterese für Stickausschlag |
#define kKeyLoopHysterese @"LoopHysterese" |
// unsigned char AchsKopplung1; // Wert: 0-250 Faktor, mit dem Gier die Achsen Roll und Nick koppelt (NickRollMitkopplung) |
#define kKeyAchsKopplung1 @"AchsKopplung1" |
// unsigned char AchsKopplung2; // Wert: 0-250 Faktor, mit dem Nick und Roll verkoppelt werden |
#define kKeyAchsKopplung2 @"AchsKopplung2" |
// unsigned char CouplingYawCorrection; // Wert: 0-250 Faktor, mit dem Nick und Roll verkoppelt werden |
#define kKeyCouplingYawCorrection @"CouplingYawCorrection" |
// unsigned char WinkelUmschlagNick; // Wert: 0-250 180?-Punkt |
#define kKeyWinkelUmschlagNick @"WinkelUmschlagNick" |
// unsigned char WinkelUmschlagRoll; // Wert: 0-250 180?-Punkt |
#define kKeyWinkelUmschlagRoll @"WinkelUmschlagRoll" |
// unsigned char GyroAccAbgleich; // 1/k (Koppel_ACC_Wirkung) |
#define kKeyGyroAccAbgleich @"GyroAccAbgleich" |
// unsigned char Driftkomp; |
#define kKeyDriftkomp @"Driftkomp" |
// unsigned char DynamicStability; |
#define kKeyDynamicStability @"DynamicStability" |
// unsigned char UserParam5; // Wert : 0-250 |
#define kKeyUserParam5 @"UserParam5" |
// unsigned char UserParam6; // Wert : 0-250 |
#define kKeyUserParam6 @"UserParam6" |
// unsigned char UserParam7; // Wert : 0-250 |
#define kKeyUserParam7 @"UserParam7" |
// unsigned char UserParam8; // Wert : 0-250 |
#define kKeyUserParam8 @"UserParam8" |
// //---Output --------------------------------------------- |
// unsigned char J16Bitmask; // for the J16 Output |
#define kKeyJ16Bitmask @"J16Bitmask" |
// unsigned char J16Timing; // for the J16 Output |
#define kKeyJ16Timing @"J16Timing" |
// unsigned char J17Bitmask; // for the J17 Output |
#define kKeyJ17Bitmask @"J17Bitmask" |
// unsigned char J17Timing; // for the J17 Output |
#define kKeyJ17Timing @"J17Timing" |
// // seit version V0.75c |
// unsigned char WARN_J16_Bitmask; // for the J16 Output |
#define kKeyWARN_J16_Bitmask @"WARN_J16_Bitmask" |
// unsigned char WARN_J17_Bitmask; // for the J17 Output |
#define kKeyWARN_J17_Bitmask @"WARN_J17_Bitmask" |
// //---NaviCtrl--------------------------------------------- |
// unsigned char NaviGpsModeControl; // Parameters for the Naviboard |
#define kKeyNaviGpsModeControl @"NaviGpsModeControl" |
// unsigned char NaviGpsGain; |
#define kKeyNaviGpsGain @"NaviGpsGain" |
// unsigned char NaviGpsP; |
#define kKeyNaviGpsP @"NaviGpsP" |
// unsigned char NaviGpsI; |
#define kKeyNaviGpsI @"NaviGpsI" |
// unsigned char NaviGpsD; |
#define kKeyNaviGpsD @"NaviGpsD" |
// unsigned char NaviGpsPLimit; |
#define kKeyNaviGpsPLimit @"NaviGpsPLimit" |
// unsigned char NaviGpsILimit; |
#define kKeyNaviGpsILimit @"NaviGpsILimit" |
// unsigned char NaviGpsDLimit; |
#define kKeyNaviGpsDLimit @"NaviGpsDLimit" |
// unsigned char NaviGpsACC; |
#define kKeyNaviGpsACC @"NaviGpsACC" |
// unsigned char NaviGpsMinSat; |
#define kKeyNaviGpsMinSat @"NaviGpsMinSat" |
// unsigned char NaviStickThreshold; |
#define kKeyNaviStickThreshold @"NaviStickThreshold" |
// unsigned char NaviWindCorrection; |
#define kKeyNaviWindCorrection @"NaviWindCorrection" |
// unsigned char NaviSpeedCompensation; |
#define kKeyNaviSpeedCompensation @"NaviSpeedCompensation" |
// unsigned char NaviOperatingRadius; |
#define kKeyNaviOperatingRadius @"NaviOperatingRadius" |
// unsigned char NaviAngleLimitation; |
#define kKeyNaviAngleLimitation @"NaviAngleLimitation" |
// unsigned char NaviPH_LoginTime; |
#define kKeyNaviPH_LoginTime @"NaviPH_LoginTime" |
// //---Ext.Ctrl--------------------------------------------- |
// unsigned char ExternalControl; // for serial Control |
#define kKeyExternalControl @"ExternalControl" |
// //------------------------------------------------ |
#define kKeyBitConfig @"BitConfig" |
// unsigned char BitConfig; // (war Loop-Cfg) Bitcodiert: 0x01=oben, 0x02=unten, 0x04=links, 0x08=rechts / wird getrennt behandelt |
// #define CFG1_LOOP_UP 0x01 |
// #define CFG1_LOOP_DOWN 0x02 |
// #define CFG1_LOOP_LEFT 0x04 |
// #define CFG1_LOOP_RIGHT 0x08 |
// #define CFG1_MOTOR_BLINK 0x10 |
// #define CFG1_MOTOR_OFF_LED1 0x20 |
// #define CFG1_MOTOR_OFF_LED2 0x40 |
#define kKeyBitConfig_LOOP_UP @"BitConfig_LOOP_UP" |
#define kKeyBitConfig_LOOP_DOWN @"BitConfig_LOOP_DOWN" |
#define kKeyBitConfig_LOOP_LEFT @"BitConfig_LOOP_LEFT" |
#define kKeyBitConfig_LOOP_RIGHT @"BitConfig_LOOP_RIGHT" |
#define kKeyBitConfig_MOTOR_BLINK @"BitConfig_MOTOR_BLINK" |
#define kKeyBitConfig_MOTOR_OFF_LED1 @"BitConfig_MOTOR_OFF_LED1" |
#define kKeyBitConfig_MOTOR_OFF_LED2 @"BitConfig_MOTOR_OFF_LED2" |
// unsigned char ServoCompInvert; // // 0x01 = Nick, 0x02 = Roll 0 oder 1 // WICHTIG!!! am Ende lassen |
#define kKeyServoCompInvert_Nick @"ServoCompInvert_Nick" |
#define kKeyServoCompInvert_ROLL @"ServoCompInvert_ROLL" |
// unsigned char ExtraConfig; // bitcodiert |
// #define CFG2_HEIGHT_LIMIT 0x01 |
// #define CFG2_VARIO_BEEP 0x02 |
// #define CFG2_SENSITIVE_RC 0x04 |
#define kKeyExtraConfig @"ExtraConfig" |
#define kKeyExtraConfig_HEIGHT_LIMIT @"ExtraConfig_HEIGHT_LIMIT" |
#define kKeyExtraConfig_VARIO_BEEP @"ExtraConfig_VARIO_BEEP" |
#define kKeyExtraConfig_SENSITIVE_RC @"ExtraConfig_SENSITIVE_RC" |
// char Name[12]; |
#define kKeyName @"Name" |
/iKopter/trunk/Classes/Communication/MKDatatypes.h |
---|
0,0 → 1,294 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
typedef enum { |
MKAddressAll =0, |
MKAddressFC =1, |
MKAddressNC =2, |
MKAddressMK3MAg=3, |
MKAddressMKGPS =0XFE |
} MKAddress; |
static const int kMaxDebugData = 32; |
typedef enum { |
MKCommandDebugValueRequest='d', |
MKCommandDebugValueResponse='D', |
MKCommandDebugLabelRequest='a', |
MKCommandDebugLabelResponse='A', |
MKCommandVersionRequest='v', |
MKCommandVersionResponse='V', |
MKCommandLcdMenuRequest='l', |
MKCommandLcdMenuResponse='L', |
MKCommandLcdRequest='h', |
MKCommandLcdResponse='H', |
MKCommandReadSettingsRequest='q', |
MKCommandReadSettingsResponse='Q', |
MKCommandWriteSettingsRequest='s', |
MKCommandWriteSettingsResponse='S', |
MKCommandChangeSettingsRequest='f', |
MKCommandChangeSettingsResponse='F', |
MKCommandChannelsValueRequest='p', |
MKCommandChannelsValueResponse='P', |
MKCommandMixerReadRequest='n', |
MKCommandMixerReadResponse='N', |
MKCommandMixerWriteRequest='m', |
MKCommandMixerWriteResponse='M', |
MKCommandRedirectRequest='u', |
MKCommandEngineTestRequest='t', |
MKCommandOsdRequest='o', |
MKCommandOsdResponse='O', |
} MKCommandId; |
typedef struct |
{ |
uint8_t SWMajor; |
uint8_t SWMinor; |
uint8_t ProtoMajor; |
uint8_t ProtoMinor; |
uint8_t SWPatch; |
uint8_t Reserved[5]; |
} VersionInfo; |
typedef struct |
{ |
uint8_t Digital[2]; |
uint16_t Analog[32]; // Debugvalues |
} DebugOut; |
typedef struct |
{ |
char Revision; |
char Name[12]; |
signed char Motor[16][4]; |
} Mixer; |
typedef struct |
{ |
unsigned char Kanalbelegung[12]; // GAS[0], GIER[1],NICK[2], ROLL[3], POTI1, POTI2, POTI3 |
unsigned char GlobalConfig; // 0x01=H?henregler aktiv,0x02=Kompass aktiv, 0x04=GPS aktiv, 0x08=Heading Hold aktiv |
unsigned char Hoehe_MinGas; // Wert : 0-100 |
unsigned char Luftdruck_D; // Wert : 0-250 |
unsigned char MaxHoehe; // Wert : 0-32 |
unsigned char Hoehe_P; // Wert : 0-32 |
unsigned char Hoehe_Verstaerkung; // Wert : 0-50 |
unsigned char Hoehe_ACC_Wirkung; // Wert : 0-250 |
unsigned char Hoehe_HoverBand; // Wert : 0-250 |
unsigned char Hoehe_GPS_Z; // Wert : 0-250 |
unsigned char Hoehe_StickNeutralPoint;// Wert : 0-250 |
unsigned char Stick_P; // Wert : 1-6 |
unsigned char Stick_D; // Wert : 0-64 |
unsigned char Gier_P; // Wert : 1-20 |
unsigned char Gas_Min; // Wert : 0-32 |
unsigned char Gas_Max; // Wert : 33-250 |
unsigned char GyroAccFaktor; // Wert : 1-64 |
unsigned char KompassWirkung; // Wert : 0-32 |
unsigned char Gyro_P; // Wert : 10-250 |
unsigned char Gyro_I; // Wert : 0-250 |
unsigned char Gyro_D; // Wert : 0-250 |
unsigned char Gyro_Gier_P; // Wert : 10-250 |
unsigned char Gyro_Gier_I; // Wert : 0-250 |
unsigned char UnterspannungsWarnung; // Wert : 0-250 |
unsigned char NotGas; // Wert : 0-250 //Gaswert bei Emp?ngsverlust |
unsigned char NotGasZeit; // Wert : 0-250 // Zeitbis auf NotGas geschaltet wird, wg. Rx-Problemen |
unsigned char Receiver; // 0= Summensignal, 1= Spektrum, 2 =Jeti, 3=ACT DSL, 4=ACT S3D |
unsigned char I_Faktor; // Wert : 0-250 |
unsigned char UserParam1; // Wert : 0-250 |
unsigned char UserParam2; // Wert : 0-250 |
unsigned char UserParam3; // Wert : 0-250 |
unsigned char UserParam4; // Wert : 0-250 |
unsigned char ServoNickControl; // Wert : 0-250 // Stellung des Servos |
unsigned char ServoNickComp; // Wert : 0-250 // Einfluss Gyro/Servo |
unsigned char ServoNickMin; // Wert : 0-250 // Anschlag |
unsigned char ServoNickMax; // Wert : 0-250 // Anschlag |
unsigned char ServoRollControl; // Wert : 0-250 // Stellung des Servos |
unsigned char ServoRollComp; // Wert : 0-250 |
unsigned char ServoRollMin; // Wert : 0-250 |
unsigned char ServoRollMax; // Wert : 0-250 |
unsigned char ServoNickRefresh; // Speed of the Servo |
unsigned char Servo3; // Value or mapping of the Servo Output |
unsigned char Servo4; // Value or mapping of the Servo Output |
unsigned char Servo5; // Value or mapping of the Servo Output |
unsigned char LoopGasLimit; // Wert: 0-250 max. Gas w?hrend Looping |
unsigned char LoopThreshold; // Wert: 0-250 Schwelle f?r Stickausschlag |
unsigned char LoopHysterese; // Wert: 0-250 Hysterese f?r Stickausschlag |
unsigned char AchsKopplung1; // Wert: 0-250 Faktor, mit dem Gier die Achsen Roll und Nick koppelt (NickRollMitkopplung) |
unsigned char AchsKopplung2; // Wert: 0-250 Faktor, mit dem Nick und Roll verkoppelt werden |
unsigned char CouplingYawCorrection; // Wert: 0-250 Faktor, mit dem Nick und Roll verkoppelt werden |
unsigned char WinkelUmschlagNick; // Wert: 0-250 180?-Punkt |
unsigned char WinkelUmschlagRoll; // Wert: 0-250 180?-Punkt |
unsigned char GyroAccAbgleich; // 1/k (Koppel_ACC_Wirkung) |
unsigned char Driftkomp; |
unsigned char DynamicStability; |
unsigned char UserParam5; // Wert : 0-250 |
unsigned char UserParam6; // Wert : 0-250 |
unsigned char UserParam7; // Wert : 0-250 |
unsigned char UserParam8; // Wert : 0-250 |
unsigned char J16Bitmask; // for the J16 Output |
unsigned char J16Timing; // for the J16 Output |
unsigned char J17Bitmask; // for the J17 Output |
unsigned char J17Timing; // for the J17 Output |
unsigned char WARN_J16_Bitmask; // for the J16 Output |
unsigned char WARN_J17_Bitmask; // for the J17 Output |
unsigned char NaviGpsModeControl; // Parameters for the Naviboard |
unsigned char NaviGpsGain; |
unsigned char NaviGpsP; |
unsigned char NaviGpsI; |
unsigned char NaviGpsD; |
unsigned char NaviGpsPLimit; |
unsigned char NaviGpsILimit; |
unsigned char NaviGpsDLimit; |
unsigned char NaviGpsACC; |
unsigned char NaviGpsMinSat; |
unsigned char NaviStickThreshold; |
unsigned char NaviWindCorrection; |
unsigned char NaviSpeedCompensation; |
unsigned char NaviOperatingRadius; |
unsigned char NaviAngleLimitation; |
unsigned char NaviPH_LoginTime; |
unsigned char ExternalControl; // for serial Control |
unsigned char BitConfig; // (war Loop-Cfg) Bitcodiert: 0x01=oben, 0x02=unten, 0x04=links, 0x08=rechts / wird getrennt behandelt |
unsigned char ServoCompInvert; // // 0x01 = Nick, 0x02 = Roll 0 oder 1 // WICHTIG!!! am Ende lassen |
unsigned char ExtraConfig; // bitcodiert |
char Name[12]; |
} MKSetting; |
#define CFG_HOEHENREGELUNG 0x01 |
#define CFG_HOEHEN_SCHALTER 0x02 |
#define CFG_HEADING_HOLD 0x04 |
#define CFG_KOMPASS_AKTIV 0x08 |
#define CFG_KOMPASS_FIX 0x10 |
#define CFG_GPS_AKTIV 0x20 |
#define CFG_ACHSENKOPPLUNG_AKTIV 0x40 |
#define CFG_DREHRATEN_BEGRENZER 0x80 |
#define CFG_LOOP_OBEN 0x01 |
#define CFG_LOOP_UNTEN 0x02 |
#define CFG_LOOP_LINKS 0x04 |
#define CFG_LOOP_RECHTS 0x08 |
#define CFG_MOTOR_BLINK 0x10 |
#define CFG_MOTOR_OFF_LED1 0x20 |
#define CFG_MOTOR_OFF_LED2 0x40 |
#define CFG_RES4 0x80 |
#define CFG2_HEIGHT_LIMIT 0x01 |
#define CFG2_VARIO_BEEP 0x02 |
#define CFG_SENSITIVE_RC 0x04 |
#define CFG2_INVERT_NICK 0x01 |
#define CFG2_INVERT_ROLL 0x02 |
////////////////////////////////////////////////////////////////////////////////////////// |
typedef struct |
{ |
int16_t AngleNick; // in 0.1 deg |
int16_t AngleRoll; // in 0.1 deg |
int16_t Heading; // in 0.1 deg |
uint8_t reserve[8]; |
} Data3D_t; |
typedef struct |
{ |
int32_t Longitude; // in 1E-7 deg |
int32_t Latitude; // in 1E-7 deg |
int32_t Altitude; // in mm |
uint8_t Status; // validity of data |
} __attribute__((packed)) GPS_Pos_t; |
typedef struct |
{ |
uint16_t Distance; // distance to target in dm |
int16_t Bearing; // course to target in deg |
} __attribute__((packed)) GPS_PosDev_t; |
typedef struct |
{ |
uint8_t Version; // version of the data structure |
GPS_Pos_t CurrentPosition; // see ubx.h for details |
GPS_Pos_t TargetPosition; |
GPS_PosDev_t TargetPositionDeviation; |
GPS_Pos_t HomePosition; |
GPS_PosDev_t HomePositionDeviation; |
uint8_t WaypointIndex; // index of current waypoints running from 0 to WaypointNumber-1 |
uint8_t WaypointNumber; // number of stored waypoints |
uint8_t SatsInUse; // number of satellites used for position solution |
int16_t Altimeter; // hight according to air pressure |
int16_t Variometer; // climb(+) and sink(-) rate |
uint16_t FlyingTime; // in seconds |
uint8_t UBat; // Battery Voltage in 0.1 Volts |
uint16_t GroundSpeed; // speed over ground in cm/s (2D) |
int16_t Heading; // current flight direction in ° as angle to north |
int16_t CompassHeading; // current compass value in ° |
int8_t AngleNick; // current Nick angle in 1° |
int8_t AngleRoll; // current Rick angle in 1° |
uint8_t RC_Quality; // RC_Quality |
uint8_t FCFlags; // Flags from FC |
uint8_t NCFlags; // Flags from NC |
uint8_t Errorcode; // 0 --> okay |
uint8_t OperatingRadius; // current operation radius around the Home Position in m |
int16_t TopSpeed; // velocity in vertical direction in cm/s |
uint8_t TargetHoldTime; // time in s to stay at the given target, counts down to 0 if target has been reached |
uint8_t RC_RSSI; // Receiver signal strength (since version 2 added) |
int16_t SetpointAltitude; // setpoint for altitude |
uint8_t Gas; // for future use |
uint16_t Current; // actual current in 0.1A steps |
uint16_t UsedCapacity; // used capacity in mAh |
} __attribute__((packed)) NaviData_t; |
typedef struct |
{ |
GPS_Pos_t Position; // the gps position of the waypoint, see ubx.h for details |
int16_t Heading; // orientation, future implementation |
uint8_t ToleranceRadius; // in meters, if the MK is within that range around the target, then the next target is triggered |
uint8_t HoldTime; // in seconds, if the was once in the tolerance area around a WP, this time defies the delay before the next WP is triggered |
uint8_t Event_Flag; // future emplementation |
uint8_t reserve[12]; // reserve |
} __attribute__((packed)) WayPoint_t; |
#define NAVIDATA_VERSION 3 |
#define NC_FLAG_FREE 0x01 |
#define NC_FLAG_PH 0x02 |
#define NC_FLAG_CH 0x04 |
#define NC_FLAG_RANGE_LIMIT 0x08 |
#define NC_FLAG_NOSERIALLINK 0x10 |
#define NC_FLAG_TARGET_REACHED 0x20 |
#define NC_FLAG_MANUAL_CONTROL 0x40 |
#define NC_FLAG_8 0x80 |
#define FCFLAG_MOTOR_RUN 0x01 |
#define FCFLAG_FLY 0x02 |
#define FCFLAG_CALIBRATE 0x04 |
#define FCFLAG_START 0x08 |
#define FCFLAG_NOTLANDUNG 0x10 |
#define FCFLAG_LOWBAT 0x20 |
#define FCFLAG_SPI_RX_ERR 0x40 |
#define FCFLAG_I2CERR 0x80 |
/iKopter/trunk/Classes/Communication/MKFakeConnection.h |
---|
0,0 → 1,49 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKConnection.h" |
@class AsyncSocket; |
@interface MKFakeConnection : NSObject<MKConnection> { |
id<MKConnectionDelegate> delegate; |
BOOL isConnected; |
int lcdDataCounter; |
int dbgDataCounter; |
int menuCounter; |
int menuPage; |
int activeSetting; |
NSMutableArray* settings; |
} |
@property (assign) id<MKConnectionDelegate> delegate; |
- (id) init; |
- (id) initWithDelegate:(id)delegate; |
@end |
/iKopter/trunk/Classes/Communication/MKFakeConnection.m |
---|
0,0 → 1,347 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "MKFakeConnection.h" |
#import "NSData+MKCommandDecode.h" |
#import "NSData+MKCommandEncode.h" |
#import "NSData+MKPayloadDecode.h" |
#import "NSData+MKPayloadEncode.h" |
#import "MKDataConstants.h" |
static NSString * const MKDummyConnectionException = @"MKDummyConnectionException"; |
@interface MKFakeConnection (Private) |
- (void) doConnect; |
- (void) doDisconnect; |
- (void) doResponseMkData:(NSData*)data; |
@end |
@implementation MKFakeConnection |
#pragma mark Properties |
@synthesize delegate; |
#pragma mark Initialization |
- (id) init { |
return [self initWithDelegate:nil]; |
} |
- (id) initWithDelegate:(id)theDelegate; |
{ |
if (self = [super init]) { |
self.delegate = theDelegate; |
NSString *path = [[NSBundle mainBundle] pathForResource:@"AllSettings" |
ofType:@"plist"]; |
settings = [[NSMutableArray arrayWithContentsOfFile:path] retain]; |
activeSetting = 3; |
} |
return self; |
} |
- (void) dealloc { |
[settings release]; |
[super dealloc]; |
} |
#pragma mark - |
#pragma mark MKInput |
- (BOOL) connectTo:(NSString *)hostOrDevice error:(NSError **)err; |
{ |
if (delegate == nil) { |
[NSException raise:MKDummyConnectionException |
format:@"Attempting to connect without a delegate. Set a delegate first."]; |
} |
NSArray * hostItems = [hostOrDevice componentsSeparatedByString:@":"]; |
if ( [hostItems count] != 2 ) { |
[NSException raise:MKDummyConnectionException |
format:@"Attempting to connect without a port. Set a port first."]; |
} |
int port = [[hostItems objectAtIndex:1] intValue]; |
NSString * host = [hostItems objectAtIndex:0]; |
DLog(@"Try to connect to %@ on port %d", host, port); |
[self performSelector:@selector(doConnect) withObject:nil afterDelay:0.5]; |
return YES; |
} |
- (BOOL) isConnected; |
{ |
return isConnected; |
} |
- (void) disconnect; |
{ |
[self performSelector:@selector(doDisconnect) withObject:nil afterDelay:0.1]; |
} |
- (void) writeMkData:(NSData *)data; |
{ |
[self performSelector:@selector(doResponseMkData:) withObject:[data retain] afterDelay:0.1]; |
} |
#pragma mark - |
- (NSData*) versionResponse; |
{ |
VersionInfo v; |
v.SWMajor = 0; |
v.SWMinor = 78; |
v.ProtoMajor = 3; |
v.ProtoMinor = 1; |
v.SWPatch = 3; |
NSData * payload = [NSData dataWithBytes:(void*)&v length:sizeof(v)]; |
return payload; |
} |
- (NSData*) debugResponse; |
{ |
DebugOut d; |
NSData * payload = [NSData dataWithBytes:(void*)&d length:sizeof(d)]; |
return payload; |
} |
- (NSData*) channelResponse; |
{ |
int16_t data[26]; |
for (int i=0; i<26; i++) { |
data[i]=random()%250; |
} |
NSData * payload = [NSData dataWithBytes:(void*)&data length:sizeof(data)]; |
return payload; |
} |
- (NSData*) menuResponse:(NSData*)payload { |
const char * bytes = [payload bytes]; |
uint8_t key=(uint8_t)bytes[0]; |
// uint8_t interval=(uint8_t)bytes[1]; |
if (key==0xFD) { |
menuPage++; |
} else if (key==0xFE) { |
menuPage--; |
} |
menuPage %= 16; |
menuCounter = 1; |
NSString* screen=[NSString stringWithFormat:@"Page %02d (%d)---------12345678901234567890abcdefghijklmnopqrst++++++++++++++++++++", |
menuPage,menuCounter]; |
NSData * newPayload = [screen dataUsingEncoding:NSASCIIStringEncoding]; |
[self performSelector:@selector(resendMenuResponse) withObject:nil afterDelay:0.5]; |
return newPayload; |
} |
- (void) resendMenuResponse { |
NSString* screen=[NSString stringWithFormat:@"Page %02d (%d)---------12345678901234567890abcdefghijklmnopqrst++++++++++++++++++++", |
menuPage,menuCounter]; |
NSData * newPayload = [screen dataUsingEncoding:NSASCIIStringEncoding]; |
NSData * rspData = [newPayload dataWithCommand:MKCommandLcdResponse forAddress:MKAddressFC]; |
if ( [delegate respondsToSelector:@selector(didReadMkData:)] ) { |
[delegate didReadMkData:rspData]; |
} |
if ( (++menuCounter)<2 ) { |
[self performSelector:@selector(resendMenuResponse) withObject:nil afterDelay:0.5]; |
} |
} |
- (NSData*) changeSettingsResponse:(NSData*)payload { |
const char * bytes = [payload bytes]; |
uint8_t index=(uint8_t)bytes[0]; |
activeSetting = index; |
NSData * newPayload = [NSData dataWithBytes:(void*)&index length:sizeof(index)]; |
return newPayload; |
} |
- (NSData*) writeSettingResponse:(NSData*)payload { |
NSDictionary* d = [payload decodeReadSettingResponse]; |
NSNumber* theIndex = [d objectForKey:kMKDataKeyIndex]; |
uint8_t index = [theIndex unsignedCharValue]; |
[settings replaceObjectAtIndex:index-1 withObject:d]; |
NSData * newPayload = [NSData dataWithBytes:(void*)&index length:sizeof(index)]; |
return newPayload; |
} |
- (NSData*) readSettingResponse:(NSData*)payload { |
const char * bytes = [payload bytes]; |
uint8_t index=(uint8_t)bytes[0]; |
if (index==0xFF) { |
index=activeSetting; |
} |
index--; |
NSDictionary* d = [settings objectAtIndex:index]; |
DLog(@"%@",d); |
NSData * newPayload = [NSData payloadForWriteSettingRequest:d]; |
return newPayload; |
} |
#pragma mark - |
- (void) doConnect { |
isConnected=YES; |
if ( [delegate respondsToSelector:@selector(didConnectTo:)] ) { |
[delegate didConnectTo:@"Dummy"]; |
} |
NSData * data = [NSData dataWithCommand:MKCommandVersionRequest |
forAddress:MKAddressAll |
payloadWithBytes:NULL |
length:0]; |
[self writeMkData:data]; |
} |
- (void) doDisconnect { |
isConnected=NO; |
if ( [delegate respondsToSelector:@selector(didDisconnect)] ) { |
[delegate didDisconnect]; |
} |
} |
- (void) doResponseMkData:(NSData*)data { |
if ([data isCrcOk]) { |
NSData * payload = [data payload]; |
// MKAddress address = [data address]; |
NSData * rspPayload; |
MKCommandId rspCommand; |
DLog(@"Need responde for command %c",[data command]); |
switch ([data command]) { |
/* |
case MKCommandLcdMenuResponse: |
n = MKLcdMenuNotification; |
d = [payload decodeLcdMenuResponse]; |
break; |
case MKCommandLcdResponse: |
n = MKLcdNotification; |
d = [payload decodeLcdResponse]; |
break; |
case MKCommandDebugLabelResponse: |
n = MKDebugLabelNotification; |
d = [payload decodeAnalogLabelResponse]; |
break; |
*/ |
case MKCommandLcdRequest: |
rspPayload = [self menuResponse:payload]; |
rspCommand = MKCommandLcdResponse; |
break; |
case MKCommandDebugValueRequest: |
rspPayload = [self debugResponse]; |
rspCommand = MKCommandDebugValueResponse; |
break; |
case MKCommandChannelsValueRequest: |
rspPayload = [self channelResponse]; |
rspCommand = MKCommandChannelsValueResponse; |
break; |
case MKCommandVersionRequest: |
rspPayload = [self versionResponse]; |
rspCommand = MKCommandVersionResponse; |
break; |
case MKCommandChangeSettingsRequest: |
rspPayload = [self changeSettingsResponse:payload]; |
rspCommand = MKCommandChangeSettingsResponse; |
break; |
case MKCommandReadSettingsRequest: |
rspPayload = [self readSettingResponse:payload]; |
rspCommand = MKCommandReadSettingsResponse; |
break; |
case MKCommandWriteSettingsRequest: |
rspPayload = [self writeSettingResponse:payload]; |
rspCommand = MKCommandWriteSettingsResponse; |
break; |
case MKCommandEngineTestRequest: |
DLog(@"Engine Test %@",payload); |
[data release]; |
return; |
default: |
ALog(@"Unknown command %c",[data command]); |
[data release]; |
return; |
} |
NSData * rspData = [rspPayload dataWithCommand:rspCommand forAddress:MKAddressFC]; |
if ( [delegate respondsToSelector:@selector(didReadMkData:)] ) { |
[delegate didReadMkData:rspData]; |
} |
} |
[data release]; |
} |
@end |
/iKopter/trunk/Classes/Communication/MKIpConnection.h |
---|
0,0 → 1,37 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKConnection.h" |
@class AsyncSocket; |
@interface MKIpConnection : NSObject<MKConnection> { |
AsyncSocket * asyncSocket; |
id<MKConnectionDelegate> delegate; |
} |
@end |
/iKopter/trunk/Classes/Communication/MKIpConnection.m |
---|
0,0 → 1,160 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "MKIpConnection.h" |
#import "AsyncSocket.h" |
static NSString * const MKIpConnectionException = @"MKIpConnectionException"; |
@implementation MKIpConnection |
#pragma mark Properties |
@synthesize delegate; |
#pragma mark Initialization |
- (id) init { |
return [self initWithDelegate:nil]; |
} |
- (id) initWithDelegate:(id<MKConnectionDelegate>)theDelegate; |
{ |
if (self = [super init]) { |
asyncSocket = [[AsyncSocket alloc] init]; |
[asyncSocket setDelegate:self]; |
self.delegate = theDelegate; |
} |
return self; |
} |
- (void) dealloc { |
DLog("dealloc"); |
[asyncSocket release]; |
[super dealloc]; |
} |
#pragma mark - |
#pragma mark MKInput |
- (BOOL) connectTo:(NSString *)hostOrDevice error:(NSError **)err; |
{ |
if (delegate == nil) { |
[NSException raise:MKIpConnectionException |
format:@"Attempting to connect without a delegate. Set a delegate first."]; |
} |
NSArray * hostItems = [hostOrDevice componentsSeparatedByString:@":"]; |
if ( [hostItems count] != 2 ) { |
[NSException raise:MKIpConnectionException |
format:@"Attempting to connect without a port. Set a port first."]; |
} |
int port = [[hostItems objectAtIndex:1] intValue]; |
NSString * host = [hostItems objectAtIndex:0]; |
DLog(@"Try to connect to %@ on port %d", host, port); |
return [asyncSocket connectToHost:host onPort:port withTimeout:30 error:err]; |
} |
- (BOOL) isConnected; |
{ |
return [asyncSocket isConnected]; |
} |
- (void) disconnect; |
{ |
DLog(@"Try to disconnect from %@ on port %d", [asyncSocket connectedHost], [asyncSocket connectedPort]); |
[asyncSocket disconnect]; |
} |
- (void) writeMkData:(NSData *)data; |
{ |
// NSMutableData * newData = [data mutableCopy]; |
// |
// [newData appendBytes:"\n" length:1]; |
[asyncSocket writeData:data withTimeout:-1 tag:0]; |
// [newData release]; |
} |
#pragma mark - |
#pragma mark AsyncSocketDelegate |
- (BOOL) onSocketWillConnect:(AsyncSocket *)sock { |
DLog(@"About to connect to %S", [sock connectedHost]); |
return TRUE; |
} |
- (void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; |
{ |
DLog(@"Did connect to %@ on port %d", host, port); |
if ( [delegate respondsToSelector:@selector(didConnectTo:)] ) { |
[delegate didConnectTo:host]; |
} |
DLog(@"Start reading the first data frame"); |
[sock readDataToData:[AsyncSocket CRData] withTimeout:-1 tag:0]; |
} |
- (void) onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; |
{ |
if ( [delegate respondsToSelector:@selector(didReadMkData:)] ) { |
[delegate didReadMkData:data]; |
} |
// DLog(@"Did read data %@",data); |
// |
// DLog(@"Start reading the next data frame"); |
[sock readDataToData:[AsyncSocket CRData] withTimeout:-1 tag:0]; |
} |
- (void) onSocket:(AsyncSocket *)sock didWriteDataWithTag:(long)tag; |
{ |
DLog(@"Finished writing the next data frame"); |
} |
- (void) onSocket:(AsyncSocket *)sock willDisconnectWithError:(NSError *)err; |
{ |
ALog(@"Disconnet with an error %@", err); |
if ( [delegate respondsToSelector:@selector(willDisconnectWithError:)] ) { |
[delegate willDisconnectWithError:err]; |
} |
} |
- (void) onSocketDidDisconnect:(AsyncSocket *)sock; |
{ |
DLog(@"Disconnect from %@ on port %d", [asyncSocket connectedHost], [asyncSocket connectedPort]); |
if ( [delegate respondsToSelector:@selector(didDisconnect)] ) { |
[delegate didDisconnect]; |
} |
} |
@end |
/iKopter/trunk/Classes/Communication/MKQmkIpConnection.h |
---|
0,0 → 1,32 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKIpConnection.h" |
@interface MKQmkIpConnection : MKIpConnection { |
} |
@end |
/iKopter/trunk/Classes/Communication/MKQmkIpConnection.m |
---|
0,0 → 1,65 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "MKQmkIpConnection.h" |
@implementation MKQmkIpConnection |
- (void) writeMkData:(NSData *)data; |
{ |
NSMutableData * newData = [data mutableCopy]; |
DLog(@"writeMkData, add \\n for QMK"); |
[newData appendBytes:"\n" length:1]; |
[super writeMkData:newData]; |
[newData release]; |
} |
#pragma mark - |
#pragma mark AsyncSocketDelegate |
- (void) onSocket:(AsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port; |
{ |
[super onSocket:sock didConnectToHost:host port:port]; |
NSString* bundelName=[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"]; |
DLog(@"Sent QMK message"); |
NSString* welcomeMsg = [NSString stringWithFormat:@"$:99:101:%@:0\r",bundelName]; |
[self writeMkData:[welcomeMsg dataUsingEncoding:NSASCIIStringEncoding]]; |
NSString* dataMsg = @"$:99:106:VDALBH:0\r"; |
[self writeMkData:[dataMsg dataUsingEncoding:NSASCIIStringEncoding]]; |
} |
- (void) onSocket:(AsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag; |
{ |
NSData * mkData = [data subdataWithRange:NSMakeRange(0, [data length] - 1)]; |
[super onSocket:sock didReadData:mkData withTag:tag]; |
} |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKCommandDecode.h |
---|
0,0 → 1,38 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKDatatypes.h" |
@interface NSData (MKCommandDecode) |
- (BOOL) isMkData; |
- (BOOL) isCrcOk; |
- (MKAddress) address; |
- (MKCommandId) command; |
- (NSData *) payload; |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKCommandDecode.m |
---|
0,0 → 1,160 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKCommandDecode.h" |
#define kInvalidMKCommand @"Invalid MK command" |
// /////////////////////////////////////////////////////////////////////////////// |
#pragma mark Helper funktions |
static NSData * decode64(const char * inBuffer, int length) |
{ |
unsigned char a, b, c, d; |
unsigned char x, y, z; |
int srcIdx = 0; |
int dstIdx = 0; |
NSMutableData * outData = [NSMutableData dataWithLength:length]; |
unsigned char * outBuffer = [outData mutableBytes]; |
if (inBuffer[srcIdx] != 0) |
{ |
while (length != 0) |
{ |
a = inBuffer[srcIdx++] - '='; |
b = inBuffer[srcIdx++] - '='; |
c = inBuffer[srcIdx++] - '='; |
d = inBuffer[srcIdx++] - '='; |
x = (a << 2) | (b >> 4); |
y = ((b & 0x0f) << 4) | (c >> 2); |
z = ((c & 0x03) << 6) | d; |
if (length--) outBuffer[dstIdx++] = x; else break; |
if (length--) outBuffer[dstIdx++] = y; else break; |
if (length--) outBuffer[dstIdx++] = z; else break; |
} |
} |
[outData setLength:dstIdx]; |
return outData; |
} |
// /////////////////////////////////////////////////////////////////////////////// |
@implementation NSData (MKCommandDecode) |
- (NSUInteger) frameLength; |
{ |
NSUInteger frameLength = [self length]; |
const char * frameBytes = [self bytes]; |
if (frameLength > 0 && frameBytes[frameLength - 1] == '\r') |
frameLength--; |
return frameLength; |
} |
- (BOOL) isMkData |
{ |
NSUInteger frameLength = [self frameLength]; |
const char * frameBytes = [self bytes]; |
if ( frameLength < 5) |
{ |
NSLog(@"The frame length is to short %d. Frame is invalid", frameLength); |
return NO; |
} |
if (frameBytes[0] != '#') |
{ |
NSLog(@"The frame is no MK frame"); |
return NO; |
} |
return YES; |
} |
- (BOOL) isCrcOk |
{ |
if (![self isMkData]) |
return NO; |
NSUInteger frameLength = [self frameLength]; |
const uint8_t * frameBytes = [self bytes]; |
uint8_t crc2 = frameBytes[frameLength - 1]; |
uint8_t crc1 = frameBytes[frameLength - 2]; |
int crc = 0; |
for (int i = 0; i < frameLength - 2; i++) |
{ |
crc += frameBytes[i]; |
} |
crc %= 4096; |
if (crc1 == ('=' + (crc / 64)) && crc2 == ('=' + crc % 64)) |
return YES; |
return NO; |
} |
- (MKAddress) address |
{ |
if (![self isCrcOk]) |
[NSException raise:kInvalidMKCommand format:@"cannot get the address from a invalid MK frame"]; |
const char * frameBytes = [self bytes]; |
return (MKAddress)(frameBytes[1] - 'a'); |
} |
- (MKCommandId) command |
{ |
if (![self isCrcOk]) |
[NSException raise:kInvalidMKCommand format:@"cannot get the address from a invalid MK frame"]; |
const char * frameBytes = [self bytes]; |
return frameBytes[2]; |
} |
- (NSData *) payload; |
{ |
NSUInteger frameLength = [self frameLength]; |
const char * frameBytes = [self bytes]; |
int startIndex = 3; |
int frameDataLength = frameLength - startIndex - 2; |
return decode64(frameBytes + startIndex, frameDataLength); |
} |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKCommandEncode.h |
---|
0,0 → 1,40 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKDatatypes.h" |
@interface NSData (MKCommandEncode) |
- (NSData *) dataWithCommand:(MKCommandId)aCommand forAddress:(MKAddress)aAddress; |
+ (NSData *) dataWithCommand:(MKCommandId)aCommand |
forAddress:(MKAddress)aAddress |
payloadForByte:(uint8_t)byte; |
+ (NSData *) dataWithCommand:(MKCommandId)aCommand |
forAddress:(MKAddress)aAddress |
payloadWithBytes:(const void *)bytes |
length:(NSUInteger)length; |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKCommandEncode.m |
---|
0,0 → 1,119 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKCommandEncode.h" |
// /////////////////////////////////////////////////////////////////////////////// |
#pragma mark Helper funktions |
static NSData * encode64(NSData * inData){ |
unsigned int dstIdx = 0; |
unsigned int srcIdx = 0; |
const unsigned char * inBuffer = [inData bytes]; |
unsigned int length = [inData length]; |
unsigned char a, b, c; |
int outDataLength = (length * 4) / 3; |
if (outDataLength % 4) |
outDataLength += 4 - (outDataLength % 4); |
NSMutableData * outData = [NSMutableData dataWithLength:outDataLength]; |
char * outBuffer = [outData mutableBytes]; |
while (length > 0) { |
if (length) { |
a = inBuffer[srcIdx++]; length--; |
} else a = 0; |
if (length) { |
b = inBuffer[srcIdx++]; length--; |
} else b = 0; |
if (length) { |
c = inBuffer[srcIdx++]; length--; |
} else c = 0; |
outBuffer[dstIdx++] = '=' + (a >> 2); |
outBuffer[dstIdx++] = '=' + (((a & 0x03) << 4) | ((b & 0xf0) >> 4)); |
outBuffer[dstIdx++] = '=' + (((b & 0x0f) << 2) | ((c & 0xc0) >> 6)); |
outBuffer[dstIdx++] = '=' + ( c & 0x3f); |
} |
[outData setLength:dstIdx]; |
return outData; |
} |
// /////////////////////////////////////////////////////////////////////////////// |
@implementation NSData (MKCommandEncode) |
- (NSData *) dataWithCommand:(MKCommandId)aCommand forAddress:(MKAddress)aAddress; |
{ |
NSMutableData * frameData = [NSMutableData dataWithLength:3]; |
char * frameBytes = [frameData mutableBytes]; |
frameBytes[0] = '#'; |
frameBytes[1] = 'a' + aAddress; |
frameBytes[2] = aCommand; |
[frameData appendData:encode64(self)]; |
NSUInteger frameLength = [frameData length]; |
frameBytes = [frameData mutableBytes]; |
int crc = 0; |
for (int i = 0; i < frameLength; i++) { |
crc += frameBytes[i]; |
} |
crc %= 4096; |
char tmpCrc1 = '=' + (crc / 64); |
char tmpCrc2 = ('=' + crc % 64); |
[frameData appendBytes:&tmpCrc1 length:1]; |
[frameData appendBytes:&tmpCrc2 length:1]; |
[frameData appendBytes:"\r" length:1]; |
return frameData; |
} |
+ (NSData *) dataWithCommand:(MKCommandId)aCommand forAddress:(MKAddress)aAddress payloadForByte:(uint8_t)byte { |
NSData * payload = [NSData dataWithBytes:&byte length:1]; |
return [payload dataWithCommand:aCommand forAddress:aAddress]; |
} |
+ (NSData *) dataWithCommand:(MKCommandId)aCommand |
forAddress:(MKAddress)aAddress |
payloadWithBytes:(const void *)bytes |
length:(NSUInteger)length { |
NSData * payload = [NSData dataWithBytes:bytes length:length]; |
return [payload dataWithCommand:aCommand forAddress:aAddress]; |
} |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadDecode.h |
---|
0,0 → 1,46 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
#import "MKDatatypes.h" |
@interface NSData (MKPayloadDecode) |
- (NSDictionary *) decodeLcdMenuResponseForAddress:(MKAddress)address; |
- (NSDictionary *) decodeLcdResponseForAddress:(MKAddress)address; |
- (NSDictionary *) decodeVersionResponseForAddress:(MKAddress)theAddress; |
- (NSDictionary *) decodeAnalogLabelResponseForAddress:(MKAddress)address; |
- (NSDictionary *) decodeDebugDataResponseForAddress:(MKAddress)address; |
- (NSDictionary *) decodeChannelsDataResponse; |
- (NSDictionary *) decodeOsdResponse; |
@end |
@interface NSData (MKPayloadDecodeSetting) |
- (NSDictionary *) decodeReadSettingResponse; |
- (NSDictionary *) decodeWriteSettingResponse; |
- (NSDictionary *) decodeChangeSettingResponse; |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadDecode.m |
---|
0,0 → 1,165 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKPayloadDecode.h" |
#import "MKDataConstants.h" |
static const NSString * HardwareType[] = { @"Default", @"FlightCtrl", @"NaviCtrl", @"MK3Mag" }; |
@implementation NSData (MKPayloadDecode) |
//------------------------------------------------------------------------------------------- |
- (NSString *) debugLabelWithIndex:(int *)theIndex { |
const char * bytes = [self bytes]; |
*theIndex = (int)bytes[0]; |
int dataLength = [self length] < 16 ? [self length] : 16; |
NSData * strData = [NSData dataWithBytesNoCopy:(void*)(++bytes) length:dataLength freeWhenDone:NO]; |
NSString * label = [[[NSString alloc] initWithData:strData encoding:NSASCIIStringEncoding] autorelease]; |
return [label stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; |
} |
- (NSDictionary *) decodeAnalogLabelResponseForAddress:(MKAddress)address; |
{ |
int index; |
NSString * label = [self debugLabelWithIndex:&index]; |
return [NSDictionary dictionaryWithObjectsAndKeys:label, kMKDataKeyLabel, [NSNumber numberWithInt:index], kMKDataKeyIndex, nil]; |
} |
//------------------------------------------------------------------------------------------- |
- (NSDictionary *) decodeDebugDataResponseForAddress:(MKAddress)address; |
{ |
DebugOut * debugData = (DebugOut *)[self bytes]; |
NSNumber* theAddress=[NSNumber numberWithInt:address]; |
NSMutableArray * targetArray = [[NSMutableArray alloc] initWithCapacity:32]; |
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; |
for (int i = 0; i < 32; i++) |
{ |
NSNumber *number = [NSNumber numberWithShort:debugData->Analog[i]]; |
[targetArray addObject:number]; |
} |
[pool drain]; |
NSDictionary* responseDictionary=[NSDictionary dictionaryWithObjectsAndKeys:targetArray, kMKDataKeyDebugData, |
theAddress, kMKDataKeyAddress, nil]; |
[targetArray release]; |
return responseDictionary; |
} |
//------------------------------------------------------------------------------------------- |
- (NSDictionary *) decodeLcdMenuResponseForAddress:(MKAddress)address; |
{ |
const char * bytes = [self bytes]; |
NSNumber* theAddress=[NSNumber numberWithInt:address]; |
NSNumber * menuItem = [NSNumber numberWithChar:bytes[0]]; |
NSNumber * maxMenuItem = [NSNumber numberWithChar:bytes[1]]; |
NSData * strData = [NSData dataWithBytesNoCopy:(char *)(bytes + 2) length:[self length] - 2 freeWhenDone:NO]; |
NSString * label = [[[NSString alloc] initWithData:strData encoding:NSASCIIStringEncoding] autorelease]; |
NSMutableArray * menuRows = [[NSMutableArray alloc] init]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(0, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(20, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(40, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(60, 20)]]; |
NSDictionary* d =[NSDictionary dictionaryWithObjectsAndKeys:menuItem, kMKDataKeyMenuItem, |
maxMenuItem, kMKDataKeyMaxMenuItem, |
menuRows, kMKDataKeyMenuRows, theAddress, kMKDataKeyAddress, nil]; |
[menuRows autorelease]; |
return d; |
} |
//------------------------------------------------------------------------------------------- |
- (NSDictionary *) decodeLcdResponseForAddress:(MKAddress)address; |
{ |
NSString * label = [[NSString alloc] initWithData:self encoding:NSASCIIStringEncoding]; |
NSNumber* theAddress=[NSNumber numberWithInt:address]; |
NSMutableArray * menuRows = [[NSMutableArray alloc] init]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(0, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(20, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(40, 20)]]; |
[menuRows addObject:[label substringWithRange:NSMakeRange(60, 20)]]; |
NSDictionary* d=[NSDictionary dictionaryWithObjectsAndKeys:menuRows, kMKDataKeyMenuRows, |
theAddress, kMKDataKeyAddress, nil]; |
[label release]; |
[menuRows release]; |
return d; |
} |
//------------------------------------------------------------------------------------------- |
- (NSDictionary *) decodeVersionResponseForAddress:(MKAddress)address; |
{ |
const VersionInfo * version = [self bytes]; |
NSNumber* theAddress=[NSNumber numberWithInt:address]; |
NSString * versionStr = [NSString stringWithFormat:@"%@ %d.%d %c", |
HardwareType[address], |
version->SWMajor, |
version->SWMinor, |
(version->SWPatch + 'a')]; |
NSString * versionStrShort = [NSString stringWithFormat:@"%d.%d%c", |
version->SWMajor, |
version->SWMinor, |
(version->SWPatch + 'a')]; |
return [NSDictionary dictionaryWithObjectsAndKeys:versionStr, kMKDataKeyVersion, |
versionStrShort, kMKDataKeyVersionShort, theAddress, kMKDataKeyAddress, nil]; |
} |
- (NSDictionary *) decodeChannelsDataResponse { |
return [NSDictionary dictionaryWithObjectsAndKeys:self, kMKDataKeyChannels, nil]; |
} |
- (NSDictionary *) decodeOsdResponse { |
// NSValue* value = [NSValue valueWithBytes:[self bytes] |
// objCType:@encode(NaviData_t)]; |
return [NSDictionary dictionaryWithObjectsAndKeys:self, kMKDataKeyRawValue, nil]; |
} |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadDecodeSetting.m |
---|
0,0 → 1,187 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKPayloadDecode.h" |
#import "MKDataConstants.h" |
@implementation NSData (MKPayloadDecodeSetting) |
- (NSDictionary *) decodeReadSettingResponse { |
const char * bytes = [self bytes]; |
NSNumber * index = [NSNumber numberWithChar:bytes[0]]; |
NSNumber * version = [NSNumber numberWithChar:bytes[1]]; |
MKSetting settings; |
memcpy((unsigned char *)&settings, (unsigned char *)(bytes + 2), sizeof(settings)); |
NSMutableDictionary* d = [[[NSMutableDictionary alloc] initWithCapacity:112] autorelease]; |
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; |
[d setObject:index forKey:kMKDataKeyIndex]; |
[d setObject:version forKey:kMKDataKeyVersion]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[0]] forKey:kKeyKanalbelegung00]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[1]] forKey:kKeyKanalbelegung01]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[2]] forKey:kKeyKanalbelegung02]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[3]] forKey:kKeyKanalbelegung03]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[4]] forKey:kKeyKanalbelegung04]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[5]] forKey:kKeyKanalbelegung05]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[6]] forKey:kKeyKanalbelegung06]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[7]] forKey:kKeyKanalbelegung07]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[8]] forKey:kKeyKanalbelegung08]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[9]] forKey:kKeyKanalbelegung09]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[10]] forKey:kKeyKanalbelegung10]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Kanalbelegung[11]] forKey:kKeyKanalbelegung11]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.GlobalConfig] forKey:kKeyGlobalConfig]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_HOEHENREGELUNG ) != 0] forKey:kKeyGlobalConfig_HOEHENREGELUNG ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_HOEHEN_SCHALTER ) != 0] forKey:kKeyGlobalConfig_HOEHEN_SCHALTER ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_HEADING_HOLD ) != 0] forKey:kKeyGlobalConfig_HEADING_HOLD ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_KOMPASS_AKTIV ) != 0] forKey:kKeyGlobalConfig_KOMPASS_AKTIV ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_KOMPASS_FIX ) != 0] forKey:kKeyGlobalConfig_KOMPASS_FIX ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_GPS_AKTIV ) != 0] forKey:kKeyGlobalConfig_GPS_AKTIV ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_ACHSENKOPPLUNG_AKTIV ) != 0] forKey:kKeyGlobalConfig_ACHSENKOPPLUNG_AKTIV ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.GlobalConfig & CFG_DREHRATEN_BEGRENZER ) != 0] forKey:kKeyGlobalConfig_DREHRATEN_BEGRENZER ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_MinGas] forKey:kKeyHoehe_MinGas ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Luftdruck_D] forKey:kKeyLuftdruck_D ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.MaxHoehe] forKey:kKeyMaxHoehe ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_P] forKey:kKeyHoehe_P ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_Verstaerkung] forKey:kKeyHoehe_Verstaerkung ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_ACC_Wirkung] forKey:kKeyHoehe_ACC_Wirkung ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_HoverBand] forKey:kKeyHoehe_HoverBand ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_GPS_Z] forKey:kKeyHoehe_GPS_Z ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Hoehe_StickNeutralPoint] forKey:kKeyHoehe_StickNeutralPoint ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Stick_P] forKey:kKeyStick_P ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Stick_D] forKey:kKeyStick_D ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gier_P] forKey:kKeyGier_P ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gas_Min] forKey:kKeyGas_Min ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gas_Max] forKey:kKeyGas_Max ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.GyroAccFaktor] forKey:kKeyGyroAccFaktor ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.KompassWirkung] forKey:kKeyKompassWirkung ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gyro_P] forKey:kKeyGyro_P ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gyro_I] forKey:kKeyGyro_I ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gyro_D] forKey:kKeyGyro_D ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gyro_Gier_P] forKey:kKeyGyro_Gier_P ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Gyro_Gier_I] forKey:kKeyGyro_Gier_I ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UnterspannungsWarnung] forKey:kKeyUnterspannungsWarnung ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NotGas] forKey:kKeyNotGas ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NotGasZeit] forKey:kKeyNotGasZeit ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Receiver] forKey:kKeyReceiver ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.I_Faktor] forKey:kKeyI_Faktor ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam1] forKey:kKeyUserParam1 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam2] forKey:kKeyUserParam2 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam3] forKey:kKeyUserParam3 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam4] forKey:kKeyUserParam4 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoNickControl] forKey:kKeyServoNickControl ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoNickComp] forKey:kKeyServoNickComp ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoNickMin] forKey:kKeyServoNickMin ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoNickMax] forKey:kKeyServoNickMax ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoRollControl] forKey:kKeyServoRollControl ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoRollComp] forKey:kKeyServoRollComp ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoRollMin] forKey:kKeyServoRollMin ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoRollMax] forKey:kKeyServoRollMax ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ServoNickRefresh] forKey:kKeyServoNickRefresh ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Servo3] forKey:kKeyServo3 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Servo4] forKey:kKeyServo4 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Servo5] forKey:kKeyServo5 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.LoopGasLimit] forKey:kKeyLoopGasLimit ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.LoopThreshold] forKey:kKeyLoopThreshold ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.LoopHysterese] forKey:kKeyLoopHysterese ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.AchsKopplung1] forKey:kKeyAchsKopplung1 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.AchsKopplung2] forKey:kKeyAchsKopplung2 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.CouplingYawCorrection] forKey:kKeyCouplingYawCorrection ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.WinkelUmschlagNick] forKey:kKeyWinkelUmschlagNick ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.WinkelUmschlagRoll] forKey:kKeyWinkelUmschlagRoll ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.GyroAccAbgleich] forKey:kKeyGyroAccAbgleich ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.Driftkomp] forKey:kKeyDriftkomp ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.DynamicStability] forKey:kKeyDynamicStability ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam5] forKey:kKeyUserParam5 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam6] forKey:kKeyUserParam6 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam7] forKey:kKeyUserParam7 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.UserParam8] forKey:kKeyUserParam8 ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.J16Bitmask] forKey:kKeyJ16Bitmask ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.J16Timing] forKey:kKeyJ16Timing ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.J17Bitmask] forKey:kKeyJ17Bitmask ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.J17Timing] forKey:kKeyJ17Timing ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.WARN_J16_Bitmask] forKey:kKeyWARN_J16_Bitmask ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.WARN_J17_Bitmask] forKey:kKeyWARN_J17_Bitmask ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsModeControl] forKey:kKeyNaviGpsModeControl ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsGain] forKey:kKeyNaviGpsGain ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsP] forKey:kKeyNaviGpsP ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsI] forKey:kKeyNaviGpsI ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsD] forKey:kKeyNaviGpsD ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsPLimit] forKey:kKeyNaviGpsPLimit ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsILimit] forKey:kKeyNaviGpsILimit ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsDLimit] forKey:kKeyNaviGpsDLimit ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsACC] forKey:kKeyNaviGpsACC ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviGpsMinSat] forKey:kKeyNaviGpsMinSat ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviStickThreshold] forKey:kKeyNaviStickThreshold ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviWindCorrection] forKey:kKeyNaviWindCorrection ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviSpeedCompensation] forKey:kKeyNaviSpeedCompensation ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviOperatingRadius] forKey:kKeyNaviOperatingRadius ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviAngleLimitation] forKey:kKeyNaviAngleLimitation ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.NaviPH_LoginTime] forKey:kKeyNaviPH_LoginTime ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ExternalControl] forKey:kKeyExternalControl ]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.BitConfig] forKey:kKeyBitConfig]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_LOOP_OBEN ) != 0] forKey:kKeyBitConfig_LOOP_UP ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_LOOP_UNTEN ) != 0] forKey:kKeyBitConfig_LOOP_DOWN ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_LOOP_LINKS ) != 0] forKey:kKeyBitConfig_LOOP_LEFT ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_LOOP_RECHTS ) != 0] forKey:kKeyBitConfig_LOOP_RIGHT ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_MOTOR_BLINK ) != 0] forKey:kKeyBitConfig_MOTOR_BLINK ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_MOTOR_OFF_LED1) != 0] forKey:kKeyBitConfig_MOTOR_OFF_LED1]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG_MOTOR_OFF_LED2) != 0] forKey:kKeyBitConfig_MOTOR_OFF_LED2]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG2_INVERT_NICK) != 0] forKey:kKeyServoCompInvert_Nick]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.BitConfig & CFG2_INVERT_ROLL) != 0] forKey:kKeyServoCompInvert_ROLL]; |
[d setObject:[NSNumber numberWithUnsignedChar:settings.ExtraConfig & CFG2_HEIGHT_LIMIT] forKey:kKeyExtraConfig]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.ExtraConfig & CFG2_HEIGHT_LIMIT) != 0] forKey:kKeyExtraConfig_HEIGHT_LIMIT]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.ExtraConfig & CFG2_VARIO_BEEP ) != 0] forKey:kKeyExtraConfig_VARIO_BEEP ]; |
[d setObject:[NSNumber numberWithUnsignedChar:(settings.ExtraConfig & CFG_SENSITIVE_RC ) != 0] forKey:kKeyExtraConfig_SENSITIVE_RC]; |
NSString* name=[NSString stringWithCString:settings.Name encoding:NSASCIIStringEncoding]; |
[d setObject:name forKey:kKeyName]; |
[pool drain]; |
return d; |
} |
- (NSDictionary *) decodeWriteSettingResponse { |
const char * bytes = [self bytes]; |
NSNumber * theIndex = [NSNumber numberWithChar:bytes[0]]; |
return [NSDictionary dictionaryWithObjectsAndKeys:theIndex, kMKDataKeyIndex, nil]; |
} |
- (NSDictionary *) decodeChangeSettingResponse { |
const char * bytes = [self bytes]; |
NSNumber * theIndex = [NSNumber numberWithChar:bytes[0]]; |
return [NSDictionary dictionaryWithObjectsAndKeys:theIndex, kMKDataKeyIndex, nil]; |
} |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadEncode.h |
---|
0,0 → 1,36 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import <Foundation/Foundation.h> |
@interface NSData (MKPayloadEncode) |
@end |
@interface NSData (MKPayloadEncodeSettings) |
+ (NSData *) payloadForWriteSettingRequest:(NSDictionary *)theDictionary; |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadEncode.m |
---|
0,0 → 1,29 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKPayloadEncode.h" |
@implementation NSData (MKPayloadEncode) |
@end |
/iKopter/trunk/Classes/Communication/NSData+MKPayloadEncodeSetting.m |
---|
0,0 → 1,179 |
// /////////////////////////////////////////////////////////////////////////////// |
// Copyright (C) 2010, Frank Blumenberg |
// |
// See License.txt for complete licensing and attribution information. |
// Permission is hereby granted, free of charge, to any person obtaining a copy |
// of this software and associated documentation files (the "Software"), to deal |
// in the Software without restriction, including without limitation the rights |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
// copies of the Software, and to permit persons to whom the Software is |
// furnished to do so, subject to the following conditions: |
// |
// The above copyright notice and this permission notice shall be included in all |
// copies or substantial portions of the Software. |
// |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
// THE SOFTWARE. |
// |
// /////////////////////////////////////////////////////////////////////////////// |
#import "NSData+MKPayloadEncode.h" |
#import "MKDatatypes.h" |
#import "MKDataConstants.h" |
@implementation NSData (MKPayloadEncodeSetting) |
+ (NSData *) payloadForWriteSettingRequest:(NSDictionary *)theDictionary; |
{ |
MKSetting setting; |
setting.Kanalbelegung[ 0]=[[theDictionary objectForKey:kKeyKanalbelegung00] unsignedCharValue]; |
setting.Kanalbelegung[ 1]=[[theDictionary objectForKey:kKeyKanalbelegung01] unsignedCharValue]; |
setting.Kanalbelegung[ 2]=[[theDictionary objectForKey:kKeyKanalbelegung02] unsignedCharValue]; |
setting.Kanalbelegung[ 3]=[[theDictionary objectForKey:kKeyKanalbelegung03] unsignedCharValue]; |
setting.Kanalbelegung[ 4]=[[theDictionary objectForKey:kKeyKanalbelegung04] unsignedCharValue]; |
setting.Kanalbelegung[ 5]=[[theDictionary objectForKey:kKeyKanalbelegung05] unsignedCharValue]; |
setting.Kanalbelegung[ 6]=[[theDictionary objectForKey:kKeyKanalbelegung06] unsignedCharValue]; |
setting.Kanalbelegung[ 7]=[[theDictionary objectForKey:kKeyKanalbelegung07] unsignedCharValue]; |
setting.Kanalbelegung[ 8]=[[theDictionary objectForKey:kKeyKanalbelegung08] unsignedCharValue]; |
setting.Kanalbelegung[ 9]=[[theDictionary objectForKey:kKeyKanalbelegung09] unsignedCharValue]; |
setting.Kanalbelegung[10]=[[theDictionary objectForKey:kKeyKanalbelegung10] unsignedCharValue]; |
setting.Kanalbelegung[11]=[[theDictionary objectForKey:kKeyKanalbelegung11] unsignedCharValue]; |
setting.GlobalConfig=0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_HOEHENREGELUNG ] boolValue]?CFG_HOEHENREGELUNG :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_HOEHEN_SCHALTER ] boolValue]?CFG_HOEHEN_SCHALTER :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_HEADING_HOLD ] boolValue]?CFG_HEADING_HOLD :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_KOMPASS_AKTIV ] boolValue]?CFG_KOMPASS_AKTIV :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_KOMPASS_FIX ] boolValue]?CFG_KOMPASS_FIX :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_GPS_AKTIV ] boolValue]?CFG_GPS_AKTIV :0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_ACHSENKOPPLUNG_AKTIV] boolValue]?CFG_ACHSENKOPPLUNG_AKTIV:0; |
setting.GlobalConfig|= [[theDictionary objectForKey:kKeyGlobalConfig_DREHRATEN_BEGRENZER ] boolValue]?CFG_DREHRATEN_BEGRENZER :0; |
setting.Hoehe_MinGas=[[theDictionary objectForKey:kKeyHoehe_MinGas ] unsignedCharValue]; |
setting.Luftdruck_D=[[theDictionary objectForKey:kKeyLuftdruck_D ] unsignedCharValue]; |
setting.MaxHoehe=[[theDictionary objectForKey:kKeyMaxHoehe ] unsignedCharValue]; |
setting.Hoehe_P=[[theDictionary objectForKey:kKeyHoehe_P ] unsignedCharValue]; |
setting.Hoehe_Verstaerkung=[[theDictionary objectForKey:kKeyHoehe_Verstaerkung ] unsignedCharValue]; |
setting.Hoehe_ACC_Wirkung=[[theDictionary objectForKey:kKeyHoehe_ACC_Wirkung ] unsignedCharValue]; |
setting.Hoehe_HoverBand=[[theDictionary objectForKey:kKeyHoehe_HoverBand ] unsignedCharValue]; |
setting.Hoehe_GPS_Z=[[theDictionary objectForKey:kKeyHoehe_GPS_Z ] unsignedCharValue]; |
setting.Hoehe_StickNeutralPoint=[[theDictionary objectForKey:kKeyHoehe_StickNeutralPoint ] unsignedCharValue]; |
setting.Stick_P=[[theDictionary objectForKey:kKeyStick_P ] unsignedCharValue]; |
setting.Stick_D=[[theDictionary objectForKey:kKeyStick_D ] unsignedCharValue]; |
setting.Gier_P=[[theDictionary objectForKey:kKeyGier_P ] unsignedCharValue]; |
setting.Gas_Min=[[theDictionary objectForKey:kKeyGas_Min ] unsignedCharValue]; |
setting.Gas_Max=[[theDictionary objectForKey:kKeyGas_Max ] unsignedCharValue]; |
setting.GyroAccFaktor=[[theDictionary objectForKey:kKeyGyroAccFaktor ] unsignedCharValue]; |
setting.KompassWirkung=[[theDictionary objectForKey:kKeyKompassWirkung ] unsignedCharValue]; |
setting.Gyro_P=[[theDictionary objectForKey:kKeyGyro_P ] unsignedCharValue]; |
setting.Gyro_I=[[theDictionary objectForKey:kKeyGyro_I ] unsignedCharValue]; |
setting.Gyro_D=[[theDictionary objectForKey:kKeyGyro_D ] unsignedCharValue]; |
setting.Gyro_Gier_P=[[theDictionary objectForKey:kKeyGyro_Gier_P ] unsignedCharValue]; |
setting.Gyro_Gier_I=[[theDictionary objectForKey:kKeyGyro_Gier_I ] unsignedCharValue]; |
setting.UnterspannungsWarnung=[[theDictionary objectForKey:kKeyUnterspannungsWarnung ] unsignedCharValue]; |
setting.NotGas=[[theDictionary objectForKey:kKeyNotGas ] unsignedCharValue]; |
setting.NotGasZeit=[[theDictionary objectForKey:kKeyNotGasZeit ] unsignedCharValue]; |
setting.Receiver=[[theDictionary objectForKey:kKeyReceiver ] unsignedCharValue]; |
setting.I_Faktor=[[theDictionary objectForKey:kKeyI_Faktor ] unsignedCharValue]; |
setting.UserParam1=[[theDictionary objectForKey:kKeyUserParam1 ] unsignedCharValue]; |
setting.UserParam2=[[theDictionary objectForKey:kKeyUserParam2 ] unsignedCharValue]; |
setting.UserParam3=[[theDictionary objectForKey:kKeyUserParam3 ] unsignedCharValue]; |
setting.UserParam4=[[theDictionary objectForKey:kKeyUserParam4 ] unsignedCharValue]; |
setting.ServoNickControl=[[theDictionary objectForKey:kKeyServoNickControl ] unsignedCharValue]; |
setting.ServoNickComp=[[theDictionary objectForKey:kKeyServoNickComp ] unsignedCharValue]; |
setting.ServoNickMin=[[theDictionary objectForKey:kKeyServoNickMin ] unsignedCharValue]; |
setting.ServoNickMax=[[theDictionary objectForKey:kKeyServoNickMax ] unsignedCharValue]; |
setting.ServoRollControl=[[theDictionary objectForKey:kKeyServoRollControl ] unsignedCharValue]; |
setting.ServoRollComp=[[theDictionary objectForKey:kKeyServoRollComp ] unsignedCharValue]; |
setting.ServoRollMin=[[theDictionary objectForKey:kKeyServoRollMin ] unsignedCharValue]; |
setting.ServoRollMax=[[theDictionary objectForKey:kKeyServoRollMax ] unsignedCharValue]; |
setting.ServoNickRefresh=[[theDictionary objectForKey:kKeyServoNickRefresh ] unsignedCharValue]; |
setting.Servo3=[[theDictionary objectForKey:kKeyServo3 ] unsignedCharValue]; |
setting.Servo4=[[theDictionary objectForKey:kKeyServo4 ] unsignedCharValue]; |
setting.Servo5=[[theDictionary objectForKey:kKeyServo5 ] unsignedCharValue]; |
setting.LoopGasLimit=[[theDictionary objectForKey:kKeyLoopGasLimit ] unsignedCharValue]; |
setting.LoopThreshold=[[theDictionary objectForKey:kKeyLoopThreshold ] unsignedCharValue]; |
setting.LoopHysterese=[[theDictionary objectForKey:kKeyLoopHysterese ] unsignedCharValue]; |
setting.AchsKopplung1=[[theDictionary objectForKey:kKeyAchsKopplung1 ] unsignedCharValue]; |
setting.AchsKopplung2=[[theDictionary objectForKey:kKeyAchsKopplung2 ] unsignedCharValue]; |
setting.CouplingYawCorrection=[[theDictionary objectForKey:kKeyCouplingYawCorrection ] unsignedCharValue]; |
setting.WinkelUmschlagNick=[[theDictionary objectForKey:kKeyWinkelUmschlagNick ] unsignedCharValue]; |
setting.WinkelUmschlagRoll=[[theDictionary objectForKey:kKeyWinkelUmschlagRoll ] unsignedCharValue]; |
setting.GyroAccAbgleich=[[theDictionary objectForKey:kKeyGyroAccAbgleich ] unsignedCharValue]; |
setting.Driftkomp=[[theDictionary objectForKey:kKeyDriftkomp ] unsignedCharValue]; |
setting.DynamicStability=[[theDictionary objectForKey:kKeyDynamicStability ] unsignedCharValue]; |
setting.UserParam5=[[theDictionary objectForKey:kKeyUserParam5 ] unsignedCharValue]; |
setting.UserParam6=[[theDictionary objectForKey:kKeyUserParam6 ] unsignedCharValue]; |
setting.UserParam7=[[theDictionary objectForKey:kKeyUserParam7 ] unsignedCharValue]; |
setting.UserParam8=[[theDictionary objectForKey:kKeyUserParam8 ] unsignedCharValue]; |
setting.J16Bitmask=[[theDictionary objectForKey:kKeyJ16Bitmask ] unsignedCharValue]; |
setting.J16Timing=[[theDictionary objectForKey:kKeyJ16Timing ] unsignedCharValue]; |
setting.J17Bitmask=[[theDictionary objectForKey:kKeyJ17Bitmask ] unsignedCharValue]; |
setting.J17Timing=[[theDictionary objectForKey:kKeyJ17Timing ] unsignedCharValue]; |
setting.WARN_J16_Bitmask=[[theDictionary objectForKey:kKeyWARN_J16_Bitmask ] unsignedCharValue]; |
setting.WARN_J17_Bitmask=[[theDictionary objectForKey:kKeyWARN_J17_Bitmask ] unsignedCharValue]; |
setting.NaviGpsModeControl=[[theDictionary objectForKey:kKeyNaviGpsModeControl ] unsignedCharValue]; |
setting.NaviGpsGain=[[theDictionary objectForKey:kKeyNaviGpsGain ] unsignedCharValue]; |
setting.NaviGpsP=[[theDictionary objectForKey:kKeyNaviGpsP ] unsignedCharValue]; |
setting.NaviGpsI=[[theDictionary objectForKey:kKeyNaviGpsI ] unsignedCharValue]; |
setting.NaviGpsD=[[theDictionary objectForKey:kKeyNaviGpsD ] unsignedCharValue]; |
setting.NaviGpsPLimit=[[theDictionary objectForKey:kKeyNaviGpsPLimit ] unsignedCharValue]; |
setting.NaviGpsILimit=[[theDictionary objectForKey:kKeyNaviGpsILimit ] unsignedCharValue]; |
setting.NaviGpsDLimit=[[theDictionary objectForKey:kKeyNaviGpsDLimit ] unsignedCharValue]; |
setting.NaviGpsACC=[[theDictionary objectForKey:kKeyNaviGpsACC ] unsignedCharValue]; |
setting.NaviGpsMinSat=[[theDictionary objectForKey:kKeyNaviGpsMinSat ] unsignedCharValue]; |
setting.NaviStickThreshold=[[theDictionary objectForKey:kKeyNaviStickThreshold ] unsignedCharValue]; |
setting.NaviWindCorrection=[[theDictionary objectForKey:kKeyNaviWindCorrection ] unsignedCharValue]; |
setting.NaviSpeedCompensation=[[theDictionary objectForKey:kKeyNaviSpeedCompensation ] unsignedCharValue]; |
setting.NaviOperatingRadius=[[theDictionary objectForKey:kKeyNaviOperatingRadius ] unsignedCharValue]; |
setting.NaviAngleLimitation=[[theDictionary objectForKey:kKeyNaviAngleLimitation ] unsignedCharValue]; |
setting.NaviPH_LoginTime=[[theDictionary objectForKey:kKeyNaviPH_LoginTime ] unsignedCharValue]; |
setting.ExternalControl=[[theDictionary objectForKey:kKeyExternalControl ] unsignedCharValue]; |
setting.BitConfig=0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_LOOP_UP ] boolValue]?CFG_LOOP_OBEN :0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_LOOP_DOWN ] boolValue]?CFG_LOOP_UNTEN :0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_LOOP_LEFT ] boolValue]?CFG_LOOP_LINKS :0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_LOOP_RIGHT ] boolValue]?CFG_LOOP_RECHTS :0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_MOTOR_BLINK ] boolValue]?CFG_MOTOR_BLINK :0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_MOTOR_OFF_LED1] boolValue]?CFG_MOTOR_OFF_LED1:0; |
setting.BitConfig|= [[theDictionary objectForKey:kKeyBitConfig_MOTOR_OFF_LED2] boolValue]?CFG_MOTOR_OFF_LED2:0; |
setting.ServoCompInvert=0; |
setting.ServoCompInvert|= [[theDictionary objectForKey:kKeyServoCompInvert_Nick] boolValue]?CFG2_INVERT_NICK:0; |
setting.ServoCompInvert|= [[theDictionary objectForKey:kKeyServoCompInvert_ROLL] boolValue]?CFG2_INVERT_ROLL:0; |
setting.ExtraConfig=0; |
setting.ExtraConfig|= [[theDictionary objectForKey:kKeyExtraConfig_HEIGHT_LIMIT] boolValue]?CFG2_HEIGHT_LIMIT:0; |
setting.ExtraConfig|= [[theDictionary objectForKey:kKeyExtraConfig_VARIO_BEEP ] boolValue]?CFG2_VARIO_BEEP :0; |
setting.ExtraConfig|= [[theDictionary objectForKey:kKeyExtraConfig_SENSITIVE_RC] boolValue]?CFG_SENSITIVE_RC :0; |
NSString* name=[theDictionary objectForKey:kKeyName]; |
memset(setting.Name, 0, 12); |
[name getBytes:(void*)setting.Name |
maxLength:12 |
usedLength:NULL |
encoding:NSASCIIStringEncoding |
options:0 |
range:NSMakeRange(0,[name length]) |
remainingRange:NULL]; |
unsigned char payloadData[sizeof(MKSetting)+2]; |
payloadData[0]=[[theDictionary objectForKey:kMKDataKeyIndex] unsignedCharValue]; |
payloadData[1]=[[theDictionary objectForKey:kMKDataKeyVersion] unsignedCharValue]; |
memcpy((unsigned char *)(payloadData + 2),(unsigned char *)&setting,sizeof(MKSetting)); |
return [NSData dataWithBytes:payloadData length:sizeof(payloadData)]; |
} |
@end |
/iKopter/trunk/Classes/Communication/SynthesizeSingleton.h |
---|
0,0 → 1,68 |
// |
// SynthesizeSingleton.h |
// CocoaWithLove |
// |
// Created by Matt Gallagher on 20/10/08. |
// Copyright 2009 Matt Gallagher. All rights reserved. |
// |
// Permission is given to use this source code file without charge in any |
// project, commercial or otherwise, entirely at your risk, with the condition |
// that any redistribution (in part or whole) of source code must retain |
// this copyright and permission notice. Attribution in compiled projects is |
// appreciated but not required. |
// |
#define SYNTHESIZE_SINGLETON_FOR_CLASS(classname) \ |
\ |
static classname *shared##classname = nil; \ |
\ |
+ (classname *)shared##classname \ |
{ \ |
@synchronized(self) \ |
{ \ |
if (shared##classname == nil) \ |
{ \ |
shared##classname = [[self alloc] init]; \ |
} \ |
} \ |
\ |
return shared##classname; \ |
} \ |
\ |
+ (id)allocWithZone:(NSZone *)zone \ |
{ \ |
@synchronized(self) \ |
{ \ |
if (shared##classname == nil) \ |
{ \ |
shared##classname = [super allocWithZone:zone]; \ |
return shared##classname; \ |
} \ |
} \ |
\ |
return nil; \ |
} \ |
\ |
- (id)copyWithZone:(NSZone *)zone \ |
{ \ |
return self; \ |
} \ |
\ |
- (id)retain \ |
{ \ |
return self; \ |
} \ |
\ |
- (NSUInteger)retainCount \ |
{ \ |
return NSUIntegerMax; \ |
} \ |
\ |
- (void)release \ |
{ \ |
} \ |
\ |
- (id)autorelease \ |
{ \ |
return self; \ |
} |