├── F53OSC.h ├── F53OSCBundle.h ├── F53OSCBundle.m ├── F53OSCClient.h ├── F53OSCClient.m ├── F53OSCFoundationAdditions.h ├── F53OSCMessage.h ├── F53OSCMessage.m ├── F53OSCPacket.h ├── F53OSCPacket.m ├── F53OSCParser.h ├── F53OSCParser.m ├── F53OSCProtocols.h ├── F53OSCServer.h ├── F53OSCServer.m ├── F53OSCSocket.h ├── F53OSCSocket.m ├── F53OSCTimeTag.h ├── F53OSCTimeTag.m ├── GCDAsyncSocket.h ├── GCDAsyncSocket.m ├── GCDAsyncUdpSocket.h ├── GCDAsyncUdpSocket.m ├── LICENSE.txt ├── MetatoneNetworkManager.h ├── MetatoneNetworkManager.m ├── NSData+F53OSCBlob.h ├── NSData+F53OSCBlob.m ├── NSDate+F53OSCTimeTag.h ├── NSDate+F53OSCTimeTag.m ├── NSNumber+F53OSCNumber.h ├── NSNumber+F53OSCNumber.m ├── NSString+F53OSCString.h ├── NSString+F53OSCString.m └── README.markdown /F53OSC.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSC.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCProtocols.h" 28 | #import "F53OSCParser.h" 29 | #import "F53OSCSocket.h" 30 | #import "F53OSCPacket.h" 31 | #import "F53OSCMessage.h" 32 | #import "F53OSCBundle.h" 33 | #import "F53OSCClient.h" 34 | #import "F53OSCServer.h" 35 | #import "F53OSCTimeTag.h" -------------------------------------------------------------------------------- /F53OSCBundle.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCBundle.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | #import "F53OSCPacket.h" 29 | 30 | @class F53OSCTimeTag; 31 | 32 | @interface F53OSCBundle : F53OSCPacket 33 | { 34 | F53OSCTimeTag *_timeTag; 35 | NSArray *_elements; 36 | } 37 | 38 | @property (retain) F53OSCTimeTag *timeTag; 39 | @property (retain) NSArray *elements; 40 | 41 | + (F53OSCBundle *) bundleWithTimeTag:(F53OSCTimeTag *)timeTag 42 | elements:(NSArray *)elements; ///< Elements must be an array of NSData objects; convert F53OSCMessages using their packetData method. 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /F53OSCBundle.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCBundle.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCBundle.h" 28 | #import "F53OSCTimeTag.h" 29 | #import "F53OSCFoundationAdditions.h" 30 | 31 | @implementation F53OSCBundle 32 | 33 | + (F53OSCBundle *) bundleWithTimeTag:(F53OSCTimeTag *)timeTag 34 | elements:(NSArray *)elements 35 | { 36 | F53OSCBundle *bundle = [F53OSCBundle new]; 37 | bundle.timeTag = timeTag; 38 | bundle.elements = elements; 39 | return bundle; 40 | } 41 | 42 | - (id) init 43 | { 44 | self = [super init]; 45 | if ( self ) 46 | { 47 | self.timeTag = [F53OSCTimeTag immediateTimeTag]; 48 | self.elements = [NSArray array]; 49 | } 50 | return self; 51 | } 52 | 53 | - (void) dealloc 54 | { 55 | self.timeTag = nil; 56 | self.elements = nil; 57 | } 58 | 59 | - (NSString *) description 60 | { 61 | return [NSString stringWithFormat:@"%@", self.elements]; 62 | } 63 | 64 | @synthesize timeTag = _timeTag; 65 | 66 | @synthesize elements = _elements; 67 | 68 | - (NSData *) packetData 69 | { 70 | NSMutableData *result = [[@"#bundle" oscStringData] mutableCopy]; 71 | 72 | [result appendData:[_timeTag oscTimeTagData]]; 73 | 74 | for ( NSData *element in self.elements ) 75 | { 76 | if ( ![element isKindOfClass:[NSData class]] ) 77 | { 78 | NSLog( @"Encountered an unknown bundle element of class %@. Bundles can only contain NSData elements. Skipping.", NSStringFromClass( [element class] ) ); 79 | continue; 80 | } 81 | 82 | [result appendData:[element oscBlobData]]; 83 | } 84 | 85 | return result; 86 | } 87 | 88 | @end 89 | -------------------------------------------------------------------------------- /F53OSCClient.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCClient.h 3 | // 4 | // Created by Sean Dougall on 1/20/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "F53OSCProtocols.h" 30 | #import "F53OSCSocket.h" 31 | #import "F53OSCPacket.h" 32 | 33 | #define F53_OSC_CLIENT_DEBUG 0 34 | 35 | @protocol F53OSCClientDelegate; 36 | 37 | @interface F53OSCClient : NSObject 38 | { 39 | //id _delegate; 40 | NSString *_host; 41 | UInt16 _port; 42 | BOOL _useTcp; 43 | id _userData; 44 | 45 | F53OSCSocket *_socket; 46 | NSMutableData *_readData; 47 | NSMutableDictionary *_readState; 48 | } 49 | 50 | @property (nonatomic, assign) id delegate; 51 | @property (nonatomic, copy) NSString *host; 52 | @property (nonatomic, assign) UInt16 port; 53 | @property (nonatomic, assign) BOOL useTcp; 54 | @property (nonatomic, retain) id userData; 55 | @property (nonatomic, assign) NSDictionary *state; 56 | @property (readonly) NSString *title; 57 | @property (readonly) BOOL isConnected; 58 | 59 | - (BOOL) connect; 60 | - (void) disconnect; 61 | 62 | - (void) sendPacket:(F53OSCPacket *)packet; 63 | - (void) sendPacket:(F53OSCPacket *)packet toHost:(NSString *)host onPort:(UInt16)port; // added CPM 20140129 64 | @end 65 | 66 | @protocol F53OSCClientDelegate 67 | 68 | @optional 69 | 70 | - (void) clientDidConnect:(F53OSCClient *)client; 71 | - (void) clientDidDisconnect:(F53OSCClient *)client; 72 | 73 | @end 74 | -------------------------------------------------------------------------------- /F53OSCClient.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCClient.m 3 | // 4 | // Created by Sean Dougall on 1/20/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCClient.h" 28 | #import "F53OSCParser.h" 29 | 30 | 31 | @interface F53OSCClient (Private) 32 | 33 | - (void) _destroySocket; 34 | - (void) _createSocket; 35 | 36 | @end 37 | 38 | 39 | @implementation F53OSCClient (Private) 40 | 41 | - (void) _destroySocket 42 | { 43 | [_readState removeObjectForKey:@"socket"]; 44 | 45 | [_socket disconnect]; 46 | //[_socket autorelease]; 47 | _socket = nil; 48 | } 49 | 50 | - (void) _createSocket 51 | { 52 | if ( _useTcp ) 53 | { 54 | GCDAsyncSocket *tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 55 | _socket = [F53OSCSocket socketWithTcpSocket:tcpSocket]; 56 | if ( _socket ) 57 | [_readState setObject:_socket forKey:@"socket"]; 58 | } 59 | else 60 | { 61 | GCDAsyncUdpSocket *udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 62 | _socket = [F53OSCSocket socketWithUdpSocket:udpSocket]; 63 | } 64 | _socket.host = self.host; 65 | _socket.port = self.port; 66 | } 67 | 68 | @end 69 | 70 | 71 | @implementation F53OSCClient 72 | 73 | - (id) init 74 | { 75 | self = [super init]; 76 | if ( self ) 77 | { 78 | _delegate = nil; 79 | _host = @"localhost"; 80 | _port = 53000; // QLab is 53000, Stagetracker is 57115. 81 | _useTcp = NO; 82 | _userData = nil; 83 | _socket = nil; 84 | _readData = [NSMutableData data]; 85 | _readState = [NSMutableDictionary dictionary]; 86 | } 87 | return self; 88 | } 89 | 90 | - (void) dealloc 91 | { 92 | _host = nil; 93 | _userData = nil; 94 | [self _destroySocket]; 95 | _readData = nil; 96 | _readState = nil; 97 | } 98 | 99 | - (void) encodeWithCoder:(NSCoder *)coder 100 | { 101 | [coder encodeObject:_host forKey:@"host"]; 102 | [coder encodeObject:[NSNumber numberWithUnsignedShort:_port] forKey:@"port"]; 103 | [coder encodeObject:[NSNumber numberWithBool:_useTcp] forKey:@"useTcp"]; 104 | [coder encodeObject:_userData forKey:@"userData"]; 105 | } 106 | 107 | - (id) initWithCoder:(NSCoder *)coder 108 | { 109 | self = [super init]; 110 | if ( self ) 111 | { 112 | _delegate = nil; 113 | _host = [coder decodeObjectForKey:@"host"]; 114 | _port = [[coder decodeObjectForKey:@"port"] unsignedShortValue]; 115 | _useTcp = [[coder decodeObjectForKey:@"useTcp"] boolValue]; 116 | _userData = [coder decodeObjectForKey:@"userData"]; 117 | _socket = nil; 118 | _readData = [NSMutableData data]; 119 | _readState = [NSMutableDictionary dictionary]; 120 | } 121 | return self; 122 | } 123 | 124 | - (NSString *) description 125 | { 126 | return [NSString stringWithFormat:@"[F53OSCClient %@:%u]", _host, _port ]; 127 | } 128 | 129 | @synthesize delegate = _delegate; 130 | 131 | @synthesize host = _host; 132 | 133 | - (void) setHost:(NSString *)host 134 | { 135 | if ( [host isEqualToString:@""] ) 136 | host = nil; 137 | _host = [host copy]; 138 | _socket.host = _host; 139 | } 140 | 141 | @synthesize port = _port; 142 | 143 | - (void) setPort:(UInt16)port 144 | { 145 | _port = port; 146 | _socket.port = _port; 147 | } 148 | 149 | @synthesize useTcp = _useTcp; 150 | 151 | - (void) setUseTcp:(BOOL)useTcp 152 | { 153 | if ( _useTcp == useTcp ) 154 | return; 155 | 156 | _useTcp = useTcp; 157 | 158 | [self _destroySocket]; 159 | } 160 | 161 | @synthesize userData = _userData; 162 | 163 | - (void) setUserData:(id)userData 164 | { 165 | if ( userData == [NSNull null] ) 166 | userData = nil; 167 | 168 | _userData = userData; 169 | } 170 | 171 | - (NSDictionary *) state 172 | { 173 | return @{ 174 | @"host": _host ? _host : @"", 175 | @"port": @( _port ), 176 | @"useTcp": @( _useTcp ), 177 | @"userData": ( _userData ? _userData : [NSNull null] ) 178 | }; 179 | } 180 | 181 | - (void) setState:(NSDictionary *)state 182 | { 183 | self.host = state[@"host"]; 184 | self.port = [state[@"port"] unsignedIntValue]; 185 | self.useTcp = [state[@"useTcp"] boolValue]; 186 | self.userData = state[@"userData"]; 187 | } 188 | 189 | - (NSString *) title 190 | { 191 | if ( _host && _port ) 192 | return [NSString stringWithFormat:@"%@ : %u", _host, _port ]; 193 | else 194 | return [NSString stringWithFormat:@"" ]; 195 | } 196 | 197 | - (BOOL) isConnected 198 | { 199 | return [_socket isConnected]; 200 | } 201 | 202 | - (BOOL) connect 203 | { 204 | if ( !_socket ) 205 | [self _createSocket]; 206 | if ( _socket ) 207 | return [_socket connect]; 208 | return NO; 209 | } 210 | 211 | - (void) disconnect 212 | { 213 | [_socket disconnect]; 214 | [_readData setData:[NSData data]]; 215 | [_readState setObject:@NO forKey:@"dangling_ESC"]; 216 | } 217 | 218 | - (void) sendPacket:(F53OSCPacket *)packet 219 | { 220 | if ( !_socket ) 221 | [self connect]; 222 | 223 | if ( _socket ) 224 | { 225 | if ( _socket.isTcpSocket ) 226 | [_socket.tcpSocket readDataWithTimeout:-1 tag:0]; // Listen for a potential response. 227 | [_socket sendPacket:packet]; 228 | } 229 | else 230 | { 231 | NSLog( @"Error: F53OSCClient could not send data; no socket available." ); 232 | } 233 | } 234 | 235 | // CPM added 20140129 236 | - (void) sendPacket:(F53OSCPacket *)packet toHost:(NSString *)host onPort:(UInt16)port 237 | { 238 | [self setHost:host]; 239 | [self setPort:port]; 240 | [self sendPacket:packet]; 241 | } 242 | 243 | #pragma mark - GCDAsyncSocketDelegate 244 | 245 | - (dispatch_queue_t) newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock 246 | { 247 | return NULL; 248 | } 249 | 250 | - (void) socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket 251 | { 252 | // Client objects do not accept new incoming connections. 253 | } 254 | 255 | - (void) socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port 256 | { 257 | #if F53_OSC_CLIENT_DEBUG 258 | NSLog( @"client socket %p didConnectToHost %@:%u", sock, host, port ); 259 | #endif 260 | 261 | if ( [self.delegate respondsToSelector:@selector( clientDidConnect: )] ) 262 | { 263 | [self.delegate clientDidConnect:self]; 264 | } 265 | } 266 | 267 | - (void) socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 268 | { 269 | #if F53_OSC_CLIENT_DEBUG 270 | NSLog( @"client socket %p didReadData of length %lu. tag : %lu", sock, [data length], tag ); 271 | #endif 272 | 273 | [F53OSCParser translateSlipData:data toData:_readData withState:_readState destination:_delegate]; 274 | [sock readDataWithTimeout:-1 tag:tag]; 275 | } 276 | 277 | - (void) socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 278 | { 279 | #if F53_OSC_CLIENT_DEBUG 280 | NSLog( @"client socket %p didReadPartialDataOfLength %lu. tag: %li", sock, partialLength, tag ); 281 | #endif 282 | } 283 | 284 | - (void) socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag 285 | { 286 | #if F53_OSC_CLIENT_DEBUG 287 | NSLog( @"client socket %p didWriteDataWithTag %li", sock, tag ); 288 | #endif 289 | } 290 | 291 | - (void) socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 292 | { 293 | #if F53_OSC_CLIENT_DEBUG 294 | NSLog( @"server socket %p didWritePartialDataOfLength %lu. tag: %li", sock, partialLength, tag ); 295 | #endif 296 | } 297 | 298 | - (NSTimeInterval) socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length 299 | { 300 | NSLog( @"Warning: F53OSCClient timed out when reading data." ); 301 | return 0; 302 | } 303 | 304 | - (NSTimeInterval) socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length 305 | { 306 | NSLog( @"Warning: F53OSCClient timed out when sending data." ); 307 | return 0; 308 | } 309 | 310 | - (void) socketDidCloseReadStream:(GCDAsyncSocket *)sock 311 | { 312 | #if F53_OSC_CLIENT_DEBUG 313 | NSLog( @"client socket %p didCloseReadStream", sock ); 314 | #endif 315 | 316 | [_readData setData:[NSData data]]; 317 | [_readState setObject:@NO forKey:@"dangling_ESC"]; 318 | } 319 | 320 | - (void) socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err 321 | { 322 | #if F53_OSC_CLIENT_DEBUG 323 | NSLog( @"client socket %p didDisconnect", sock ); 324 | #endif 325 | 326 | [_readData setData:[NSData data]]; 327 | [_readState setObject:@NO forKey:@"dangling_ESC"]; 328 | 329 | if ( [self.delegate respondsToSelector:@selector( clientDidDisconnect: )] ) 330 | { 331 | [self.delegate clientDidDisconnect:self]; 332 | } 333 | } 334 | 335 | - (void) socketDidSecure:(GCDAsyncSocket *)sock 336 | { 337 | } 338 | 339 | #pragma mark - GCDAsyncUdpSocketDelegate 340 | 341 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address 342 | { 343 | } 344 | 345 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error 346 | { 347 | } 348 | 349 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag 350 | { 351 | } 352 | 353 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error 354 | { 355 | } 356 | 357 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext 358 | { 359 | } 360 | 361 | - (void) udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error 362 | { 363 | } 364 | 365 | @end 366 | -------------------------------------------------------------------------------- /F53OSCFoundationAdditions.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCFoundationAdditions.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSDate+F53OSCTimeTag.h" 28 | #import "NSString+F53OSCString.h" 29 | #import "NSData+F53OSCBlob.h" 30 | #import "NSNumber+F53OSCNumber.h" -------------------------------------------------------------------------------- /F53OSCMessage.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCMessage.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "F53OSCPacket.h" 30 | 31 | /// 32 | /// Example usage: 33 | /// 34 | /// F53OSCMessage *msg = [F53OSCMessage messageWithAddressPattern:@"/address/of/thing" 35 | /// arguments:[NSArray arrayWithObjects: 36 | /// [NSNumber numberWithInteger:x], 37 | /// [NSNumber numberWithFloat:y], 38 | /// @"z", 39 | /// nil]]; 40 | /// 41 | 42 | @interface F53OSCMessage : F53OSCPacket 43 | { 44 | NSString *_addressPattern; 45 | NSString *_typeTagString; 46 | NSArray *_arguments; 47 | id _userData; 48 | } 49 | 50 | + (BOOL) legalAddressComponent:(NSString *)addressComponent; 51 | + (BOOL) legalAddress:(NSString *)address; 52 | + (BOOL) legalMethod:(NSString *)method; 53 | + (F53OSCMessage *) messageWithString:(NSString *)string; 54 | + (F53OSCMessage *) messageWithAddressPattern:(NSString *)addressPattern 55 | arguments:(NSArray *)arguments; 56 | + (F53OSCMessage *) messageWithAddressPattern:(NSString *)addressPattern 57 | arguments:(NSArray *)arguments 58 | replySocket:(F53OSCSocket *)replySocket; 59 | 60 | @property (nonatomic, copy) NSString *addressPattern; 61 | @property (nonatomic, retain) NSString *typeTagString; ///< This is normally constructed from the incoming arguments array. 62 | @property (nonatomic, retain) NSArray *arguments; ///< May contain NSString, NSData, or NSNumber objects. This could be extended in the future, but those three cover the four mandatory OSC types. 63 | @property (nonatomic, retain) id userData; 64 | 65 | - (NSArray *) addressParts; 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /F53OSCMessage.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCMessage.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | // Reference information: http://opensoundcontrol.org/spec-1_0-examples 27 | // 28 | 29 | #import "F53OSCMessage.h" 30 | #import "F53OSCServer.h" 31 | #import "F53OSCFoundationAdditions.h" 32 | 33 | @implementation F53OSCMessage 34 | 35 | static NSCharacterSet *_LEGAL_ADDRESS_CHARACTERS = nil; 36 | static NSCharacterSet *_LEGAL_METHOD_CHARACTERS = nil; 37 | 38 | + (void) initialize 39 | { 40 | if ( !_LEGAL_ADDRESS_CHARACTERS ) 41 | { 42 | NSString *legalAddressChars = [NSString stringWithFormat:@"%@/*?[]{,}", [F53OSCServer validCharsForOSCMethod]]; 43 | _LEGAL_ADDRESS_CHARACTERS = [NSCharacterSet characterSetWithCharactersInString:legalAddressChars]; 44 | 45 | _LEGAL_METHOD_CHARACTERS = [NSCharacterSet characterSetWithCharactersInString:[F53OSCServer validCharsForOSCMethod]]; 46 | } 47 | } 48 | 49 | + (BOOL) legalAddressComponent:(NSString *)addressComponent 50 | { 51 | if ( addressComponent == nil ) 52 | return NO; 53 | 54 | if ( [_LEGAL_ADDRESS_CHARACTERS isSupersetOfSet:[NSCharacterSet characterSetWithCharactersInString:addressComponent]] ) 55 | { 56 | if ( [addressComponent length] >= 1 ) 57 | return YES; 58 | } 59 | 60 | return NO; 61 | } 62 | 63 | + (BOOL) legalAddress:(NSString *)address 64 | { 65 | if ( address == nil ) 66 | return NO; 67 | 68 | if ( [_LEGAL_ADDRESS_CHARACTERS isSupersetOfSet:[NSCharacterSet characterSetWithCharactersInString:address]] ) 69 | { 70 | if ( [address length] >= 1 && [address characterAtIndex:0] == '/' ) 71 | return YES; 72 | } 73 | 74 | return NO; 75 | } 76 | 77 | + (BOOL) legalMethod:(NSString *)method 78 | { 79 | if ( method == nil ) 80 | return NO; 81 | 82 | if ( [_LEGAL_METHOD_CHARACTERS isSupersetOfSet:[NSCharacterSet characterSetWithCharactersInString:method]] ) 83 | return YES; 84 | else 85 | return NO; 86 | } 87 | 88 | + (F53OSCMessage *) messageWithString:(NSString *)string 89 | { 90 | if ( string == nil ) 91 | return nil; 92 | 93 | string = [string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; 94 | 95 | if ( [string isEqualToString:@""] ) 96 | return nil; 97 | 98 | // Pull out address. 99 | NSString *address = [[string componentsSeparatedByString:@" "] objectAtIndex:0]; 100 | if ( ![self legalAddress:address] ) 101 | return nil; 102 | 103 | // Pull out arguments... 104 | 105 | // Create a working copy and place a token for each escaped " character. 106 | NSString *QUOTE_CHAR_TOKEN = @"•"; 107 | NSString *workingArguments = [string substringFromIndex:[address length]]; 108 | workingArguments = [workingArguments stringByReplacingOccurrencesOfString:@"\\\"" withString:QUOTE_CHAR_TOKEN]; 109 | 110 | // The remaining " characters signify quoted string arguments; they should be paired up. 111 | NSArray *splitOnQuotes = [workingArguments componentsSeparatedByString:@"\""]; 112 | if ( [splitOnQuotes count] % 2 != 1 ) 113 | return nil; // not matching quotes 114 | 115 | NSString *QUOTE_STRING_TOKEN = @"∞"; 116 | NSMutableArray *allQuotedStrings = [NSMutableArray array]; 117 | for ( int i = 1; i < [splitOnQuotes count]; i += 2 ) 118 | { 119 | // Pull out each quoted string, which will be at each odd index. 120 | NSString *quotedString = [splitOnQuotes objectAtIndex:i]; 121 | [allQuotedStrings addObject:quotedString]; 122 | 123 | // Place a token for the quote we just pulled. 124 | workingArguments = [workingArguments stringByReplacingOccurrencesOfString: 125 | [NSString stringWithFormat:@"\"%@\"", quotedString] withString:QUOTE_STRING_TOKEN]; 126 | } 127 | 128 | // The working arguments have now been tokenized enough to process. 129 | // Expand the tokens and store the final array of arguments. 130 | NSMutableArray *finalArgs = [NSMutableArray array]; 131 | NSArray *tokenArgs = [workingArguments componentsSeparatedByString:@" "]; 132 | int token_index = 0; 133 | for ( NSString *arg in tokenArgs ) 134 | { 135 | if ( [arg isEqual:@""] ) // artifact of componentsSeparatedByString 136 | continue; 137 | if ( [arg isEqual:QUOTE_STRING_TOKEN] ) 138 | { 139 | NSString *detokenized = [[allQuotedStrings objectAtIndex:token_index] 140 | stringByReplacingOccurrencesOfString:QUOTE_CHAR_TOKEN withString:@"\""]; 141 | [finalArgs addObject:detokenized]; 142 | token_index++; 143 | } 144 | else if ( [arg isEqual:QUOTE_CHAR_TOKEN] ) 145 | { 146 | [finalArgs addObject:@"\""]; 147 | } 148 | else 149 | { 150 | NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init]; 151 | [formatter setLocale:[NSLocale currentLocale]]; 152 | [formatter setAllowsFloats:YES]; 153 | 154 | NSNumber *number = [formatter numberFromString:arg]; 155 | if ( number == nil ) 156 | [finalArgs addObject:arg]; 157 | else 158 | [finalArgs addObject:number]; 159 | } 160 | } 161 | 162 | NSArray *arguments = [NSArray arrayWithArray:finalArgs]; 163 | 164 | return [F53OSCMessage messageWithAddressPattern:address arguments:arguments]; 165 | } 166 | 167 | + (F53OSCMessage *) messageWithAddressPattern:(NSString *)addressPattern 168 | arguments:(NSArray *)arguments 169 | { 170 | return [F53OSCMessage messageWithAddressPattern:addressPattern arguments:arguments replySocket:nil]; 171 | } 172 | 173 | + (F53OSCMessage *) messageWithAddressPattern:(NSString *)addressPattern 174 | arguments:(NSArray *)arguments 175 | replySocket:(F53OSCSocket *)replySocket 176 | { 177 | F53OSCMessage *msg = [F53OSCMessage new]; 178 | msg.addressPattern = addressPattern; 179 | msg.arguments = arguments; 180 | msg.replySocket = replySocket; 181 | return msg; 182 | } 183 | 184 | - (id) init 185 | { 186 | self = [super init]; 187 | if ( self ) 188 | { 189 | self.addressPattern = @"/"; 190 | self.typeTagString = @","; 191 | self.arguments = [NSArray array]; 192 | self.userData = nil; 193 | } 194 | return self; 195 | } 196 | 197 | - (void) dealloc 198 | { 199 | self.addressPattern = nil; 200 | self.typeTagString = nil; 201 | self.arguments = nil; 202 | self.userData = nil; 203 | } 204 | 205 | - (void) encodeWithCoder:(NSCoder *)coder 206 | { 207 | [coder encodeObject:_addressPattern forKey:@"addressPattern"]; 208 | [coder encodeObject:_typeTagString forKey:@"typeTagString"]; 209 | [coder encodeObject:_arguments forKey:@"arguments"]; 210 | } 211 | 212 | - (id) initWithCoder:(NSCoder *)coder 213 | { 214 | self = [super init]; 215 | if ( self ) 216 | { 217 | [self setAddressPattern:[coder decodeObjectForKey:@"addressPattern"]]; 218 | [self setTypeTagString:[coder decodeObjectForKey:@"typeTagString"]]; 219 | [self setArguments:[coder decodeObjectForKey:@"arguments"]]; 220 | } 221 | return self; 222 | } 223 | 224 | - (id) copyWithZone:(NSZone *)zone 225 | { 226 | F53OSCMessage *copy = [super copyWithZone:zone]; 227 | copy->_addressPattern = [_addressPattern copyWithZone:zone]; 228 | copy->_typeTagString = [_typeTagString copyWithZone:zone]; 229 | copy->_arguments = [_arguments copyWithZone:zone]; 230 | copy->_userData = [_userData copyWithZone:zone]; 231 | return copy; 232 | } 233 | 234 | - (NSString *) description 235 | { 236 | NSMutableString *description = [NSMutableString stringWithString:self.addressPattern]; 237 | for ( id arg in self.arguments ) 238 | { 239 | [description appendFormat:@" %@", [arg description]]; 240 | } 241 | return [NSString stringWithString:description]; 242 | } 243 | 244 | - (BOOL) isEqual:(id)object 245 | { 246 | if ( [object isMemberOfClass:[self class]] ) 247 | { 248 | F53OSCMessage *otherObject = object; 249 | if ( [[otherObject addressPattern] isEqualToString:_addressPattern] 250 | && [[otherObject arguments] isEqualToArray:_arguments] ) 251 | { 252 | return YES; 253 | } 254 | } 255 | return NO; 256 | } 257 | 258 | @synthesize addressPattern = _addressPattern; 259 | 260 | - (void) setAddressPattern:(NSString *)addressPattern 261 | { 262 | if ( addressPattern == nil || 263 | [addressPattern length] == 0 || 264 | [addressPattern characterAtIndex:0] != '/' ) 265 | { 266 | return; 267 | } 268 | 269 | _addressPattern = [addressPattern copy]; 270 | } 271 | 272 | @synthesize typeTagString = _typeTagString; 273 | 274 | @synthesize arguments = _arguments; 275 | 276 | - (void) setArguments:(NSArray *)argArray 277 | { 278 | NSMutableArray *newArgs = [NSMutableArray array]; 279 | NSMutableString *newTypes = [NSMutableString stringWithString:@","]; 280 | for ( id obj in argArray ) 281 | { 282 | if ( [obj isKindOfClass:[NSString class]] ) 283 | { 284 | [newTypes appendString:@"s"]; 285 | [newArgs addObject:obj]; 286 | } 287 | else if ( [obj isKindOfClass:[NSData class]] ) 288 | { 289 | [newTypes appendString:@"b"]; 290 | [newArgs addObject:obj]; 291 | } 292 | else if ( [obj isKindOfClass:[NSNumber class]] ) 293 | { 294 | CFNumberType numberType = CFNumberGetType( (CFNumberRef)obj ); 295 | switch ( numberType ) 296 | { 297 | case kCFNumberSInt8Type: 298 | case kCFNumberSInt16Type: 299 | case kCFNumberSInt32Type: 300 | case kCFNumberSInt64Type: 301 | case kCFNumberCharType: 302 | case kCFNumberShortType: 303 | case kCFNumberIntType: 304 | case kCFNumberLongType: 305 | case kCFNumberLongLongType: 306 | case kCFNumberNSIntegerType: 307 | [newTypes appendString:@"i"]; break; 308 | case kCFNumberFloat32Type: 309 | case kCFNumberFloat64Type: 310 | case kCFNumberFloatType: 311 | case kCFNumberDoubleType: 312 | case kCFNumberCGFloatType: 313 | [newTypes appendString:@"f"]; break; 314 | default: 315 | NSLog( @"Number with unrecognized type: %i (value = %@).", (int)numberType, obj ); 316 | continue; 317 | } 318 | [newArgs addObject:obj]; 319 | } 320 | } 321 | self.typeTagString = [newTypes copy]; 322 | _arguments = [newArgs copy]; 323 | } 324 | 325 | @synthesize userData = _userData; 326 | 327 | - (NSArray *) addressParts 328 | { 329 | NSMutableArray *parts = [NSMutableArray arrayWithArray:[self.addressPattern componentsSeparatedByString:@"/"]]; 330 | [parts removeObjectAtIndex:0]; 331 | return [NSArray arrayWithArray:parts]; 332 | } 333 | 334 | - (NSData *) packetData 335 | { 336 | NSMutableData *result = [[self.addressPattern oscStringData] mutableCopy]; 337 | 338 | [result appendData:[self.typeTagString oscStringData]]; 339 | 340 | for ( id obj in self.arguments ) 341 | { 342 | if ( [obj isKindOfClass:[NSString class]] ) 343 | { 344 | [result appendData:[(NSString *)obj oscStringData]]; 345 | } 346 | else if ( [obj isKindOfClass:[NSData class]] ) 347 | { 348 | [result appendData:[(NSData *)obj oscBlobData]]; 349 | } 350 | else if ( [obj isKindOfClass:[NSNumber class]] ) 351 | { 352 | SInt32 intValue; 353 | CFNumberType numberType = CFNumberGetType( (CFNumberRef)obj ); 354 | switch ( numberType ) 355 | { 356 | case kCFNumberSInt8Type: 357 | case kCFNumberSInt16Type: 358 | case kCFNumberSInt32Type: 359 | case kCFNumberSInt64Type: 360 | case kCFNumberCharType: 361 | case kCFNumberShortType: 362 | case kCFNumberIntType: 363 | case kCFNumberLongType: 364 | case kCFNumberLongLongType: 365 | case kCFNumberNSIntegerType: 366 | intValue = [(NSNumber *)obj oscIntValue]; 367 | [result appendBytes:&intValue length:sizeof( SInt32 )]; 368 | break; 369 | case kCFNumberFloat32Type: 370 | case kCFNumberFloat64Type: 371 | case kCFNumberFloatType: 372 | case kCFNumberDoubleType: 373 | case kCFNumberCGFloatType: 374 | intValue = [(NSNumber *)obj oscFloatValue]; 375 | [result appendBytes:&intValue length:sizeof( SInt32 )]; 376 | break; 377 | default: 378 | NSLog( @"Number with unrecognized type: %i (value = %@).", (int)numberType, obj ); 379 | continue; 380 | } 381 | } 382 | } 383 | 384 | return result; 385 | } 386 | 387 | @end 388 | -------------------------------------------------------------------------------- /F53OSCPacket.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCPacket.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "F53OSCSocket.h" 30 | 31 | @interface F53OSCPacket : NSObject 32 | { 33 | F53OSCSocket *_replySocket; ///< If this message was received from a client, this is the socket to use to reply. 34 | } 35 | 36 | @property (retain) F53OSCSocket *replySocket; 37 | 38 | - (NSData *) packetData; 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /F53OSCPacket.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCPacket.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCPacket.h" 28 | 29 | 30 | @implementation F53OSCPacket 31 | 32 | - (void) dealloc 33 | { 34 | //[_replySocket release]; 35 | _replySocket = nil; 36 | 37 | //[super dealloc]; 38 | } 39 | 40 | - (id) copyWithZone:(NSZone *)zone 41 | { 42 | F53OSCPacket *copy = [[self class] allocWithZone:zone]; 43 | copy->_replySocket = _replySocket; 44 | return copy; 45 | } 46 | 47 | @synthesize replySocket = _replySocket; 48 | 49 | - (NSData *) packetData 50 | { 51 | // Defined by subclasses. 52 | return nil; 53 | } 54 | 55 | @end 56 | -------------------------------------------------------------------------------- /F53OSCParser.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCParser.h 3 | // 4 | // Created by Christopher Ashworth on 1/30/13. 5 | // 6 | // Copyright (c) 2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "F53OSCProtocols.h" 30 | 31 | @class F53OSCSocket; 32 | 33 | @interface F53OSCParser : NSObject 34 | 35 | + (void) processOscData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket; 36 | 37 | + (void) translateSlipData:(NSData *)slipData toData:(NSMutableData *)data withState:(NSMutableDictionary *)state destination:(id )destination; 38 | 39 | @end 40 | -------------------------------------------------------------------------------- /F53OSCParser.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCParser.m 3 | // 4 | // Created by Christopher Ashworth on 1/30/13. 5 | // 6 | // Copyright (c) 2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCParser.h" 28 | #import "F53OSCMessage.h" 29 | #import "F53OSCSocket.h" 30 | #import "F53OSCFoundationAdditions.h" 31 | 32 | #define END 0300 /* indicates end of packet */ 33 | #define ESC 0333 /* indicates byte stuffing */ 34 | #define ESC_END 0334 /* ESC ESC_END means END data byte */ 35 | #define ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */ 36 | 37 | 38 | @interface F53OSCParser (Private) 39 | 40 | + (void) processMessageData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket; 41 | + (void) processBundleData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket; 42 | 43 | @end 44 | 45 | @implementation F53OSCParser (private) 46 | 47 | + (void) processMessageData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket; 48 | { 49 | NSUInteger length = [data length]; 50 | const char *buffer = [data bytes]; 51 | 52 | NSUInteger lengthOfRemainingBuffer = length; 53 | NSUInteger dataLength = 0; 54 | NSString *addressPattern = [NSString stringWithOSCStringBytes:buffer maxLength:lengthOfRemainingBuffer length:&dataLength]; 55 | if ( addressPattern == nil || dataLength == 0 || dataLength > length ) 56 | { 57 | NSLog( @"Error: Unable to parse OSC method address." ); 58 | return; 59 | } 60 | 61 | buffer += dataLength; 62 | lengthOfRemainingBuffer -= dataLength; 63 | 64 | NSMutableArray *args = [NSMutableArray array]; 65 | BOOL hasArguments = (lengthOfRemainingBuffer > 0); 66 | if ( hasArguments && buffer[0] == ',' ) 67 | { 68 | NSString *typeTag = [NSString stringWithOSCStringBytes:buffer maxLength:lengthOfRemainingBuffer length:&dataLength]; 69 | if ( typeTag == nil ) 70 | { 71 | NSLog( @"Error: Unable to parse type tag for OSC method %@", addressPattern ); 72 | return; 73 | } 74 | buffer += dataLength; 75 | lengthOfRemainingBuffer -= dataLength; 76 | 77 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 78 | { 79 | NSLog( @"Incoming OSC message:" ); 80 | NSLog( @" %@", addressPattern ); 81 | } 82 | 83 | NSInteger numArgs = [typeTag length] - 1; 84 | if ( numArgs > 0 ) 85 | { 86 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 87 | NSLog( @" arguments:" ); 88 | 89 | for ( int i = 1; i < numArgs + 1; i++ ) 90 | { 91 | NSString *stringArg = nil; 92 | NSData *dataArg = nil; 93 | NSNumber *numberArg = nil; 94 | 95 | char type = [typeTag characterAtIndex:i]; // (index starts at 1 because first char is ",") 96 | switch ( type ) 97 | { 98 | case 's': 99 | stringArg = [NSString stringWithOSCStringBytes:buffer maxLength:lengthOfRemainingBuffer length:&dataLength]; 100 | if ( stringArg ) 101 | { 102 | [args addObject:stringArg]; 103 | buffer += dataLength; 104 | lengthOfRemainingBuffer -= dataLength; 105 | 106 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 107 | NSLog( @" string: \"%@\"", stringArg ); 108 | } 109 | else 110 | { 111 | NSLog( @"Error: Unable to parse string argument for OSC method %@", addressPattern ); 112 | return; 113 | } 114 | break; 115 | case 'b': 116 | dataArg = [NSData dataWithOSCBlobBytes:buffer maxLength:lengthOfRemainingBuffer length:&dataLength]; 117 | if ( dataArg ) 118 | { 119 | [args addObject:dataArg]; 120 | buffer += dataLength + 4; 121 | lengthOfRemainingBuffer -= (dataLength + 4); 122 | 123 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 124 | NSLog( @" blob: %@", dataArg ); 125 | } 126 | else 127 | { 128 | NSLog( @"Error: Unable to parse blob argument for OSC method %@", addressPattern ); 129 | return; 130 | } 131 | break; 132 | case 'i': 133 | numberArg = [NSNumber numberWithOSCIntBytes:buffer maxLength:lengthOfRemainingBuffer]; 134 | if ( numberArg ) 135 | { 136 | [args addObject:numberArg]; 137 | buffer += 4; 138 | lengthOfRemainingBuffer -= 4; 139 | 140 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 141 | NSLog( @" int: %@", numberArg ); 142 | } 143 | else 144 | { 145 | NSLog( @"Error: Unable to parse int argument for OSC method %@", addressPattern ); 146 | return; 147 | } 148 | break; 149 | case 'f': 150 | numberArg = [NSNumber numberWithOSCFloatBytes:buffer maxLength:lengthOfRemainingBuffer]; 151 | if ( numberArg ) 152 | { 153 | [args addObject:numberArg]; 154 | buffer += 4; 155 | lengthOfRemainingBuffer -= 4; 156 | 157 | if ( [[NSUserDefaults standardUserDefaults] boolForKey:@"debugIncomingOSC"] ) 158 | NSLog( @" float: %@", numberArg ); 159 | } 160 | else 161 | { 162 | NSLog( @"Error: Unable to parse float argument for OSC method %@", addressPattern ); 163 | return; 164 | } 165 | break; 166 | default: 167 | NSLog( @"Error: Unrecognized type '%c' found in type tag for OSC method %@", type, addressPattern ); 168 | return; 169 | } 170 | } 171 | } 172 | } 173 | 174 | [destination takeMessage:[F53OSCMessage messageWithAddressPattern:addressPattern arguments:args replySocket:socket]]; 175 | } 176 | 177 | + (void) processBundleData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket; 178 | { 179 | NSUInteger length = [data length]; 180 | const char *buffer = [data bytes]; 181 | 182 | NSUInteger lengthOfRemainingBuffer = length; 183 | NSUInteger dataLength = 0; 184 | NSString *bundlePrefix = [NSString stringWithOSCStringBytes:buffer maxLength:lengthOfRemainingBuffer length:&dataLength]; 185 | if ( bundlePrefix == nil || dataLength == 0 || dataLength > length ) 186 | { 187 | NSLog( @"Error: Unable to parse OSC bundle prefix." ); 188 | return; 189 | } 190 | 191 | if ( [bundlePrefix isEqualToString:@"#bundle"] ) 192 | { 193 | buffer += dataLength; 194 | lengthOfRemainingBuffer -= dataLength; 195 | 196 | if ( lengthOfRemainingBuffer > 8 ) 197 | { 198 | //F53OSCTimeTag *timetag = [F53OSCTimeTag timeTagWithOSCTimeBytes:buffer]; 199 | buffer += 8; // We're not currently using the time tag so we just skip it. 200 | lengthOfRemainingBuffer -= 8; 201 | 202 | while ( lengthOfRemainingBuffer > sizeof( UInt32 ) ) 203 | { 204 | UInt32 elementLength = *((UInt32 *)buffer); 205 | elementLength = OSSwapBigToHostInt32( elementLength ); 206 | buffer += sizeof( UInt32 ); 207 | lengthOfRemainingBuffer -= sizeof( UInt32 ); 208 | 209 | if ( elementLength > lengthOfRemainingBuffer ) 210 | { 211 | NSLog( @"Error: A message in the OSC bundle claimed to be larger than the bundle itself." ); 212 | return; 213 | } 214 | 215 | if ( buffer[0] == '/' ) // OSC message 216 | { 217 | [self processMessageData:[NSData dataWithBytesNoCopy:(void *)buffer length:elementLength freeWhenDone:NO] 218 | forDestination:destination 219 | replyToSocket:socket]; 220 | } 221 | else if ( buffer[0] == '#' ) // OSC bundle 222 | { 223 | [self processBundleData:[NSData dataWithBytesNoCopy:(void *)buffer length:elementLength freeWhenDone:NO] 224 | forDestination:destination 225 | replyToSocket:socket]; 226 | } 227 | else 228 | { 229 | NSLog( @"Error: Bundle contained unrecognized OSC message of length %u.", (unsigned int)elementLength ); 230 | return; 231 | } 232 | 233 | buffer += elementLength; 234 | lengthOfRemainingBuffer -= elementLength; 235 | } 236 | } 237 | else 238 | { 239 | NSLog( @"Warning: Received an empty OSC bundle message." ); 240 | } 241 | } 242 | else 243 | { 244 | NSLog( @"Error: Received an invalid OSC bundle message." ); 245 | } 246 | } 247 | 248 | @end 249 | 250 | @implementation F53OSCParser 251 | 252 | + (void) processOscData:(NSData *)data forDestination:(id )destination replyToSocket:(F53OSCSocket *)socket 253 | { 254 | if ( data == nil || destination == nil ) 255 | return; 256 | 257 | NSUInteger length = [data length]; 258 | if ( length == 0 ) 259 | return; 260 | 261 | const char *buffer = [data bytes]; 262 | 263 | if ( buffer[0] == '/' ) // OSC message 264 | { 265 | [self processMessageData:data forDestination:destination replyToSocket:socket]; 266 | } 267 | else if ( buffer[0] == '#' ) // OSC bundle 268 | { 269 | [self processBundleData:data forDestination:destination replyToSocket:socket]; 270 | } 271 | else 272 | { 273 | NSLog( @"Error: Unrecognized OSC message of length %lu.", (unsigned long)length ); 274 | } 275 | } 276 | 277 | + (void) translateSlipData:(NSData *)slipData 278 | toData:(NSMutableData *)data 279 | withState:(NSMutableDictionary *)state 280 | destination:(id )destination 281 | { 282 | // Incoming OSC messages are framed using the SLIP protocol: http://www.rfc-editor.org/rfc/rfc1055.txt 283 | 284 | F53OSCSocket *socket = [state objectForKey:@"socket"]; 285 | if ( socket == nil ) 286 | { 287 | NSLog( @"Error: F53OSCParser can not translate SLIP data without a socket." ); 288 | return; 289 | } 290 | 291 | BOOL dangling_ESC = [[state objectForKey:@"dangling_ESC"] boolValue]; 292 | 293 | Byte end[1] = {END}; 294 | Byte esc[1] = {ESC}; 295 | 296 | NSUInteger length = [slipData length]; 297 | const Byte *buffer = [slipData bytes]; 298 | for ( NSUInteger index = 0; index < length; index++ ) 299 | { 300 | if ( dangling_ESC ) 301 | { 302 | dangling_ESC = NO; 303 | [state setObject:@NO forKey:@"dangling_ESC"]; 304 | if ( buffer[index] == ESC_END ) 305 | [data appendBytes:end length:1]; 306 | else if ( buffer[index] == ESC_ESC ) 307 | [data appendBytes:esc length:1]; 308 | else // Protocol violation. Pass the byte along and hope for the best. 309 | [data appendBytes:&(buffer[index]) length:1]; 310 | } 311 | else if ( buffer[index] == END ) 312 | { 313 | // The data is now a complete message. 314 | //NSLog( @"socket %p dispatching OSC data of length %lu", sock, [data length] ); 315 | [F53OSCParser processOscData:[NSData dataWithData:data] forDestination:destination replyToSocket:socket]; 316 | [data setData:[NSData data]]; 317 | } 318 | else if ( buffer[index] == ESC ) 319 | { 320 | if ( index + 1 < length ) 321 | { 322 | index++; 323 | if ( buffer[index] == ESC_END ) 324 | [data appendBytes:end length:1]; 325 | else if ( buffer[index] == ESC_ESC ) 326 | [data appendBytes:esc length:1]; 327 | else // Protocol violation. Pass the byte along and hope for the best. 328 | [data appendBytes:&(buffer[index]) length:1]; 329 | } 330 | else 331 | { 332 | // The incoming raw data stopped in the middle of an escape sequence. 333 | [state setObject:@YES forKey:@"dangling_ESC"]; 334 | } 335 | } 336 | else 337 | { 338 | [data appendBytes:&(buffer[index]) length:1]; 339 | } 340 | } 341 | } 342 | @end 343 | -------------------------------------------------------------------------------- /F53OSCProtocols.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCProtocols.h 3 | // 4 | // Created by Christopher Ashworth on 1/30/13. 5 | // 6 | // Copyright (c) 2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | @class F53OSCMessage; 28 | 29 | @protocol F53OSCPacketDestination 30 | 31 | - (void) takeMessage:(F53OSCMessage *)message; 32 | 33 | @end 34 | 35 | -------------------------------------------------------------------------------- /F53OSCServer.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCServer.h 3 | // 4 | // Created by Sean Dougall on 3/23/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "F53OSC.h" 30 | 31 | #define F53_OSC_SERVER_DEBUG 0 32 | 33 | @interface F53OSCServer : NSObject 34 | { 35 | //id _delegate; 36 | UInt16 _port; 37 | UInt16 _udpReplyPort; 38 | F53OSCSocket *_tcpSocket; 39 | F53OSCSocket *_udpSocket; 40 | NSMutableDictionary *_activeTcpSockets; // F53OSCSockets keyed by index of when the connection was accepted. 41 | NSMutableDictionary *_activeData; // NSMutableData keyed by index; buffers the incoming data. 42 | NSMutableDictionary *_activeState; // NSMutableDictionary keyed by index; stores state of incoming data. 43 | NSInteger _activeIndex; 44 | } 45 | 46 | + (NSString *) validCharsForOSCMethod; 47 | + (NSPredicate *) predicateForAttribute:(NSString *)attributeName 48 | matchingOSCPattern:(NSString *)pattern; 49 | 50 | @property (nonatomic, assign) id delegate; 51 | @property (nonatomic, assign) UInt16 port; 52 | @property (nonatomic, assign) UInt16 udpReplyPort; 53 | 54 | - (BOOL) startListening; 55 | - (void) stopListening; 56 | 57 | @end 58 | -------------------------------------------------------------------------------- /F53OSCServer.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCServer.m 3 | // 4 | // Created by Sean Dougall on 3/23/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCServer.h" 28 | #import "F53OSCFoundationAdditions.h" 29 | 30 | 31 | @interface F53OSCServer (Private) 32 | 33 | + (NSString *) _stringWithSpecialRegexCharactersEscaped:(NSString *)string; 34 | 35 | @end 36 | 37 | 38 | @implementation F53OSCServer (Private) 39 | 40 | /// 41 | /// Escape characters that are special in regex (ICU v3) but not special in OSC. 42 | /// Regex docs: http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Metacharacters 43 | /// OSC docs: http://opensoundcontrol.org/spec-1_0 44 | /// 45 | + (NSString *) _stringWithSpecialRegexCharactersEscaped:(NSString *)string 46 | { 47 | string = [string stringByReplacingOccurrencesOfString:@"\\" withString:@"\\\\"]; // Do this first! 48 | string = [string stringByReplacingOccurrencesOfString:@"+" withString:@"\\+"]; 49 | string = [string stringByReplacingOccurrencesOfString:@"-" withString:@"\\-"]; 50 | string = [string stringByReplacingOccurrencesOfString:@"(" withString:@"\\("]; 51 | string = [string stringByReplacingOccurrencesOfString:@")" withString:@"\\)"]; 52 | string = [string stringByReplacingOccurrencesOfString:@"^" withString:@"\\^"]; 53 | string = [string stringByReplacingOccurrencesOfString:@"$" withString:@"\\$"]; 54 | string = [string stringByReplacingOccurrencesOfString:@"|" withString:@"\\|"]; 55 | string = [string stringByReplacingOccurrencesOfString:@"." withString:@"\\."]; 56 | return string; 57 | } 58 | 59 | @end 60 | 61 | 62 | @implementation F53OSCServer 63 | 64 | + (NSString *) validCharsForOSCMethod 65 | { 66 | return @"\"$%&'()+-.0123456789:;<=>@ABCDEFGHIJKLMNOPQRSTUVWXYZ\\^_`abcdefghijklmnopqrstuvwxyz|~!"; 67 | } 68 | 69 | + (NSPredicate *) predicateForAttribute:(NSString *)attributeName 70 | matchingOSCPattern:(NSString *)pattern 71 | { 72 | //NSLog( @"pattern : %@", pattern ); 73 | 74 | // Basic validity checks. 75 | if ( [[pattern componentsSeparatedByString:@"["] count] != [[pattern componentsSeparatedByString:@"]"] count] ) 76 | return nil; 77 | if ( [[pattern componentsSeparatedByString:@"{"] count] != [[pattern componentsSeparatedByString:@"}"] count] ) 78 | return nil; 79 | 80 | NSString *validOscChars = [F53OSCServer _stringWithSpecialRegexCharactersEscaped:[F53OSCServer validCharsForOSCMethod]]; 81 | NSString *wildCard = [NSString stringWithFormat:@"[%@]*", validOscChars]; 82 | NSString *oneChar = [NSString stringWithFormat:@"[%@]{1}?", validOscChars]; 83 | 84 | // Escape characters that are special in regex (ICU v3) but not special in OSC. 85 | pattern = [F53OSCServer _stringWithSpecialRegexCharactersEscaped:pattern]; 86 | //NSLog( @"cleaned : %@", pattern ); 87 | 88 | // Replace characters that are special in OSC with their equivalents in regex (ICU v3). 89 | pattern = [pattern stringByReplacingOccurrencesOfString:@"*" withString:wildCard]; 90 | pattern = [pattern stringByReplacingOccurrencesOfString:@"?" withString:oneChar]; 91 | pattern = [pattern stringByReplacingOccurrencesOfString:@"[!" withString:@"[^"]; 92 | pattern = [pattern stringByReplacingOccurrencesOfString:@"{" withString:@"("]; 93 | pattern = [pattern stringByReplacingOccurrencesOfString:@"}" withString:@")"]; 94 | pattern = [pattern stringByReplacingOccurrencesOfString:@"," withString:@"|"]; 95 | //NSLog( @"translated: %@", pattern ); 96 | 97 | // MATCHES: 98 | // The left hand expression equals the right hand expression 99 | // using a regex-style comparison according to ICU v3. See: 100 | // http://icu.sourceforge.net/userguide/regexp.html 101 | // http://userguide.icu-project.org/strings/regexp#TOC-Regular-Expression-Metacharacters 102 | 103 | return [NSPredicate predicateWithFormat:@"%K MATCHES %@", attributeName, pattern]; 104 | } 105 | 106 | - (id) init 107 | { 108 | self = [super init]; 109 | if ( self ) 110 | { 111 | GCDAsyncSocket *tcpSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 112 | GCDAsyncUdpSocket *udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 113 | 114 | _delegate = nil; 115 | _port = 0; 116 | _udpReplyPort = 0; 117 | _udpSocket = [F53OSCSocket socketWithUdpSocket:udpSocket]; 118 | _tcpSocket = [F53OSCSocket socketWithTcpSocket:tcpSocket]; 119 | 120 | _activeTcpSockets = [NSMutableDictionary dictionaryWithCapacity:1]; 121 | _activeData = [NSMutableDictionary dictionaryWithCapacity:1]; 122 | _activeState = [NSMutableDictionary dictionaryWithCapacity:1]; 123 | _activeIndex = 0; 124 | } 125 | return self; 126 | } 127 | 128 | - (void) dealloc 129 | { 130 | [self stopListening]; 131 | _tcpSocket = nil; 132 | _udpSocket = nil; 133 | _activeTcpSockets = nil; 134 | _activeData = nil; 135 | _activeState = nil; 136 | } 137 | 138 | @synthesize delegate = _delegate; 139 | 140 | @synthesize port = _port; 141 | 142 | - (void) setPort:(UInt16)port 143 | { 144 | _port = port; 145 | 146 | [_tcpSocket stopListening]; 147 | [_udpSocket stopListening]; 148 | _tcpSocket.port = _port; 149 | _udpSocket.port = _port; 150 | } 151 | 152 | @synthesize udpReplyPort = _udpReplyPort; 153 | 154 | - (BOOL) startListening 155 | { 156 | BOOL success; 157 | success = [_tcpSocket startListening]; 158 | if ( success ) 159 | success = [_udpSocket startListening]; 160 | return success; 161 | } 162 | 163 | - (void) stopListening 164 | { 165 | [_tcpSocket stopListening]; 166 | [_udpSocket stopListening]; 167 | } 168 | 169 | #pragma mark - GCDAsyncSocketDelegate 170 | 171 | - (dispatch_queue_t) newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock 172 | { 173 | return NULL; 174 | } 175 | 176 | - (void) socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket 177 | { 178 | #if F53_OSC_SERVER_DEBUG 179 | NSLog( @"server socket %p didAcceptNewSocket %p", sock, newSocket ); 180 | #endif 181 | 182 | F53OSCSocket *activeSocket = [F53OSCSocket socketWithTcpSocket:newSocket]; 183 | activeSocket.host = newSocket.connectedHost; 184 | activeSocket.port = newSocket.connectedPort; 185 | 186 | NSNumber *key = [NSNumber numberWithInteger:_activeIndex]; 187 | [_activeTcpSockets setObject:activeSocket forKey:key]; 188 | [_activeData setObject:[NSMutableData data] forKey:key]; 189 | [_activeState setObject:[@{ @"socket": activeSocket, @"dangling_ESC": @NO } mutableCopy] forKey:key]; 190 | 191 | [newSocket readDataWithTimeout:-1 tag:_activeIndex]; 192 | 193 | _activeIndex++; 194 | } 195 | 196 | - (void) socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port 197 | { 198 | } 199 | 200 | - (void) socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag 201 | { 202 | #if F53_OSC_SERVER_DEBUG 203 | NSLog( @"server socket %p didReadData of length %lu. tag : %lu", sock, [data length], tag ); 204 | #endif 205 | 206 | NSMutableData *activeData = [_activeData objectForKey:[NSNumber numberWithInteger:tag]]; 207 | NSMutableDictionary *activeState = [_activeState objectForKey:[NSNumber numberWithInteger:tag]]; 208 | if ( activeData && activeState ) 209 | { 210 | [F53OSCParser translateSlipData:data toData:activeData withState:activeState destination:_delegate]; 211 | [sock readDataWithTimeout:-1 tag:tag]; 212 | } 213 | } 214 | 215 | - (void) socket:(GCDAsyncSocket *)sock didReadPartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 216 | { 217 | #if F53_OSC_SERVER_DEBUG 218 | NSLog( @"server socket %p didReadPartialDataOfLength %lu. tag: %li", sock, partialLength, tag ); 219 | #endif 220 | } 221 | 222 | - (void) socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag 223 | { 224 | #if F53_OSC_SERVER_DEBUG 225 | NSLog( @"server socket %p didWriteDataWithTag: %li", sock, tag ); 226 | #endif 227 | } 228 | 229 | - (void) socket:(GCDAsyncSocket *)sock didWritePartialDataOfLength:(NSUInteger)partialLength tag:(long)tag 230 | { 231 | #if F53_OSC_SERVER_DEBUG 232 | NSLog( @"server socket %p didWritePartialDataOfLength %lu", sock, partialLength ); 233 | #endif 234 | } 235 | 236 | - (NSTimeInterval) socket:(GCDAsyncSocket *)sock shouldTimeoutReadWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length 237 | { 238 | NSLog( @"Warning: F53OSCServer timed out when reading TCP data." ); 239 | return 0; 240 | } 241 | 242 | - (NSTimeInterval) socket:(GCDAsyncSocket *)sock shouldTimeoutWriteWithTag:(long)tag elapsed:(NSTimeInterval)elapsed bytesDone:(NSUInteger)length 243 | { 244 | NSLog( @"Warning: F53OSCServer timed out when writing TCP data." ); 245 | return 0; 246 | } 247 | 248 | - (void) socketDidCloseReadStream:(GCDAsyncSocket *)sock 249 | { 250 | #if F53_OSC_SERVER_DEBUG 251 | NSLog( @"server socket %p didCloseReadStream", sock ); 252 | #endif 253 | } 254 | 255 | - (void) socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err 256 | { 257 | #if F53_OSC_SERVER_DEBUG 258 | NSLog( @"server socket %p didDisconnect", sock ); 259 | #endif 260 | 261 | id keyOfDyingSocket = nil; 262 | for ( id key in [_activeTcpSockets allKeys] ) 263 | { 264 | F53OSCSocket *socket = [_activeTcpSockets objectForKey:key]; 265 | if ( socket.tcpSocket == sock ) 266 | { 267 | keyOfDyingSocket = key; 268 | break; 269 | } 270 | } 271 | 272 | if ( keyOfDyingSocket ) 273 | { 274 | [_activeTcpSockets removeObjectForKey:keyOfDyingSocket]; 275 | [_activeData removeObjectForKey:keyOfDyingSocket]; 276 | [_activeState removeObjectForKey:keyOfDyingSocket]; 277 | } 278 | else 279 | { 280 | NSLog( @"Error: F53OSCServer couldn't find the F53OSCSocket associated with the disconnecting TCP socket." ); 281 | } 282 | } 283 | 284 | - (void) socketDidSecure:(GCDAsyncSocket *)sock 285 | { 286 | } 287 | 288 | #pragma mark - GCDAsyncUdpSocketDelegate 289 | 290 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address 291 | { 292 | } 293 | 294 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error 295 | { 296 | } 297 | 298 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag 299 | { 300 | } 301 | 302 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError *)error 303 | { 304 | } 305 | 306 | - (void) udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext 307 | { 308 | GCDAsyncUdpSocket *rawReplySocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()]; 309 | F53OSCSocket *replySocket = [F53OSCSocket socketWithUdpSocket:rawReplySocket]; 310 | replySocket.host = [GCDAsyncUdpSocket hostFromAddress:address]; 311 | replySocket.port = _udpReplyPort; 312 | 313 | [F53OSCParser processOscData:data forDestination:_delegate replyToSocket:replySocket]; 314 | } 315 | 316 | - (void) udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error 317 | { 318 | } 319 | 320 | @end 321 | -------------------------------------------------------------------------------- /F53OSCSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCSocket.h 3 | // 4 | // Created by Christopher Ashworth on 1/28/13. 5 | // 6 | // Copyright (c) 2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | #import "GCDAsyncSocket.h" 30 | #import "GCDAsyncUdpSocket.h" 31 | 32 | @class F53OSCPacket; 33 | 34 | /// 35 | /// An F53OSCSocket object represents either a TCP socket or UDP socket, but never both at the same time. 36 | /// 37 | 38 | @interface F53OSCSocket : NSObject 39 | { 40 | GCDAsyncSocket *_tcpSocket; 41 | GCDAsyncUdpSocket *_udpSocket; 42 | NSString *_host; 43 | UInt16 _port; 44 | } 45 | 46 | + (F53OSCSocket *) socketWithTcpSocket:(GCDAsyncSocket *)socket; 47 | + (F53OSCSocket *) socketWithUdpSocket:(GCDAsyncUdpSocket *)socket; 48 | 49 | - (id) initWithTcpSocket:(GCDAsyncSocket *)socket; 50 | - (id) initWithUdpSocket:(GCDAsyncUdpSocket *)socket; 51 | 52 | @property (readonly) GCDAsyncSocket *tcpSocket; 53 | @property (readonly) GCDAsyncUdpSocket *udpSocket; 54 | @property (readonly) BOOL isTcpSocket; 55 | @property (readonly) BOOL isUdpSocket; 56 | @property (nonatomic, copy) NSString *host; 57 | @property (nonatomic, assign) UInt16 port; 58 | 59 | - (BOOL) startListening; 60 | - (void) stopListening; 61 | 62 | - (BOOL) connect; 63 | - (void) disconnect; 64 | - (BOOL) isConnected; 65 | 66 | - (void) sendPacket:(F53OSCPacket *)packet; 67 | 68 | @end 69 | -------------------------------------------------------------------------------- /F53OSCSocket.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCSocket.m 3 | // 4 | // Created by Christopher Ashworth on 1/28/13. 5 | // 6 | // Copyright (c) 2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCSocket.h" 28 | #import "F53OSCPacket.h" 29 | 30 | #define TIMEOUT 3 31 | 32 | #define END 0300 /* indicates end of packet */ 33 | #define ESC 0333 /* indicates byte stuffing */ 34 | #define ESC_END 0334 /* ESC ESC_END means END data byte */ 35 | #define ESC_ESC 0335 /* ESC ESC_ESC means ESC data byte */ 36 | 37 | 38 | @implementation F53OSCSocket 39 | 40 | + (F53OSCSocket *) socketWithTcpSocket:(GCDAsyncSocket *)socket 41 | { 42 | return [[F53OSCSocket alloc] initWithTcpSocket:socket]; 43 | } 44 | 45 | + (F53OSCSocket *) socketWithUdpSocket:(GCDAsyncUdpSocket *)socket 46 | { 47 | return [[F53OSCSocket alloc] initWithUdpSocket:socket]; 48 | } 49 | 50 | - (id) initWithTcpSocket:(GCDAsyncSocket *)socket; 51 | { 52 | self = [super init]; 53 | if ( self ) 54 | { 55 | _tcpSocket = socket; 56 | _udpSocket = nil; 57 | _host = @"localhost"; 58 | _port = 0; 59 | } 60 | return self; 61 | } 62 | 63 | - (id) initWithUdpSocket:(GCDAsyncUdpSocket *)socket 64 | { 65 | self = [super init]; 66 | if ( self ) 67 | { 68 | _tcpSocket = nil; 69 | _udpSocket = socket; 70 | _host = @"localhost"; 71 | _port = 0; 72 | } 73 | return self; 74 | } 75 | 76 | - (void) dealloc 77 | { 78 | [_tcpSocket setDelegate:nil]; 79 | [_tcpSocket disconnect]; 80 | //[_tcpSocket release]; 81 | _tcpSocket = nil; 82 | 83 | [_udpSocket setDelegate:nil]; 84 | //[_udpSocket release]; 85 | _udpSocket = nil; 86 | 87 | //[_host release]; 88 | _host = nil; 89 | 90 | //[super dealloc]; 91 | } 92 | 93 | - (NSString *) description 94 | { 95 | if ( self.isTcpSocket ) 96 | return [NSString stringWithFormat:@"[TCP socket %@:%u isConnected = %i]", _host, _port, self.isConnected ]; 97 | else 98 | return [NSString stringWithFormat:@"[UDP socket %@:%u]", _host, _port ]; 99 | } 100 | 101 | - (GCDAsyncSocket *) tcpSocket 102 | { 103 | return _tcpSocket; 104 | } 105 | 106 | - (GCDAsyncUdpSocket *) udpSocket 107 | { 108 | return _udpSocket; 109 | } 110 | 111 | - (BOOL) isTcpSocket 112 | { 113 | return ( _tcpSocket != nil ); 114 | } 115 | 116 | - (BOOL) isUdpSocket 117 | { 118 | return ( _udpSocket != nil ); 119 | } 120 | 121 | @synthesize host = _host; 122 | 123 | @synthesize port = _port; 124 | 125 | - (BOOL) startListening 126 | { 127 | if ( _tcpSocket ) 128 | return [_tcpSocket acceptOnPort:_port error:nil]; 129 | 130 | if ( _udpSocket ) 131 | { 132 | if ( [_udpSocket bindToPort:_port error:nil] ) 133 | return [_udpSocket beginReceiving:nil]; 134 | else 135 | return NO; 136 | } 137 | 138 | return NO; 139 | } 140 | 141 | - (void) stopListening 142 | { 143 | if ( _tcpSocket ) 144 | [_tcpSocket disconnectAfterWriting]; 145 | 146 | if ( _udpSocket ) 147 | [_udpSocket close]; 148 | } 149 | 150 | - (BOOL) connect 151 | { 152 | if ( _tcpSocket ) 153 | { 154 | if ( _host && _port ) 155 | return [_tcpSocket connectToHost:_host onPort:_port error:nil]; 156 | else 157 | return NO; 158 | } 159 | 160 | if ( _udpSocket ) 161 | return YES; 162 | 163 | return NO; 164 | } 165 | 166 | - (void) disconnect 167 | { 168 | [_tcpSocket disconnect]; 169 | } 170 | 171 | - (BOOL) isConnected 172 | { 173 | if ( _tcpSocket ) 174 | return [_tcpSocket isConnected]; 175 | 176 | if ( _udpSocket ) 177 | return YES; 178 | 179 | return NO; 180 | } 181 | 182 | - (void) sendPacket:(F53OSCPacket *)packet 183 | { 184 | //NSLog( @"%@ sending packet: %@", self, packet ); 185 | 186 | NSData *data = [packet packetData]; 187 | 188 | //NSLog( @"%@ sending message with native length: %li", self, [data length] ); 189 | 190 | if ( _tcpSocket ) 191 | { 192 | // Outgoing OSC messages are framed using the double END SLIP protocol: http://www.rfc-editor.org/rfc/rfc1055.txt 193 | 194 | NSMutableData *slipData = [NSMutableData data]; 195 | Byte esc_end[2] = {ESC, ESC_END}; 196 | Byte esc_esc[2] = {ESC, ESC_ESC}; 197 | Byte end[1] = {END}; 198 | 199 | [slipData appendBytes:end length:1]; 200 | NSUInteger length = [data length]; 201 | const Byte *buffer = [data bytes]; 202 | for ( NSUInteger index = 0; index < length; index++ ) 203 | { 204 | if ( buffer[index] == END ) 205 | [slipData appendBytes:esc_end length:2]; 206 | else if ( buffer[index] == ESC ) 207 | [slipData appendBytes:esc_esc length:2]; 208 | else 209 | [slipData appendBytes:&(buffer[index]) length:1]; 210 | } 211 | [slipData appendBytes:end length:1]; 212 | 213 | [_tcpSocket writeData:slipData withTimeout:TIMEOUT tag:[slipData length]]; 214 | } 215 | else if ( _udpSocket ) 216 | { 217 | [_udpSocket sendData:data toHost:_host port:_port withTimeout:TIMEOUT tag:0]; 218 | [_udpSocket closeAfterSending]; 219 | } 220 | } 221 | 222 | @end 223 | -------------------------------------------------------------------------------- /F53OSCTimeTag.h: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCTimeTag.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | @interface F53OSCTimeTag : NSObject 30 | { 31 | UInt32 _seconds; 32 | UInt32 _fraction; 33 | } 34 | 35 | @property (assign) UInt32 seconds; 36 | @property (assign) UInt32 fraction; 37 | 38 | + (F53OSCTimeTag *) timeTagWithDate:(NSDate *)date; 39 | + (F53OSCTimeTag *) immediateTimeTag; 40 | 41 | - (NSData *) oscTimeTagData; 42 | + (F53OSCTimeTag *) timeTagWithOSCTimeBytes:(char *)buf; 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /F53OSCTimeTag.m: -------------------------------------------------------------------------------- 1 | // 2 | // F53OSCTimeTag.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "F53OSCTimeTag.h" 28 | #import "NSDate+F53OSCTimeTag.h" 29 | 30 | @implementation F53OSCTimeTag 31 | 32 | @synthesize seconds = _seconds; 33 | 34 | @synthesize fraction = _fraction; 35 | 36 | + (F53OSCTimeTag *) timeTagWithDate:(NSDate *)date 37 | { 38 | double fractionsPerSecond = (double)0xffffffff; 39 | F53OSCTimeTag *result = [F53OSCTimeTag new]; 40 | double secondsSince1900 = [date timeIntervalSince1970] + 2208988800; 41 | result.seconds = ((UInt64)secondsSince1900) & 0xffffffff; 42 | result.fraction = (UInt32)( fmod( secondsSince1900, 1.0 ) * fractionsPerSecond ); 43 | //return [result autorelease]; 44 | return result; 45 | } 46 | 47 | + (F53OSCTimeTag *) immediateTimeTag 48 | { 49 | F53OSCTimeTag *result = [F53OSCTimeTag new]; 50 | result.seconds = 0; 51 | result.fraction = 1; 52 | //return [result autorelease]; 53 | return result; 54 | } 55 | 56 | - (NSData *) oscTimeTagData 57 | { 58 | UInt32 seconds = OSSwapHostToBigInt32( _seconds ); 59 | UInt32 fraction = OSSwapHostToBigInt32( _fraction ); 60 | NSMutableData *data = [NSMutableData data]; 61 | [data appendBytes:&seconds length:sizeof( UInt32 )]; 62 | [data appendBytes:&fraction length:sizeof( UInt32 )]; 63 | //return [[data copy] autorelease]; 64 | return [data copy]; 65 | } 66 | 67 | + (F53OSCTimeTag *) timeTagWithOSCTimeBytes:(char *)buf 68 | { 69 | if ( buf == NULL ) 70 | return nil; 71 | 72 | UInt32 seconds = *((UInt32 *)buf); 73 | buf += sizeof( UInt32 ); 74 | UInt32 fraction = *((UInt32 *)buf); 75 | F53OSCTimeTag *result = [[F53OSCTimeTag alloc] init]; 76 | result.seconds = OSSwapBigToHostInt32( seconds ); 77 | result.fraction = OSSwapBigToHostInt32( fraction ); 78 | return result; 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /GCDAsyncUdpSocket.h: -------------------------------------------------------------------------------- 1 | // 2 | // GCDAsyncUdpSocket 3 | // 4 | // This class is in the public domain. 5 | // Originally created by Robbie Hanson of Deusty LLC. 6 | // Updated and maintained by Deusty LLC and the Apple development community. 7 | // 8 | // https://github.com/robbiehanson/CocoaAsyncSocket 9 | // 10 | 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | extern NSString *const GCDAsyncUdpSocketException; 18 | extern NSString *const GCDAsyncUdpSocketErrorDomain; 19 | 20 | extern NSString *const GCDAsyncUdpSocketQueueName; 21 | extern NSString *const GCDAsyncUdpSocketThreadName; 22 | 23 | typedef NS_ENUM(NSInteger, GCDAsyncUdpSocketError) { 24 | GCDAsyncUdpSocketNoError = 0, // Never used 25 | GCDAsyncUdpSocketBadConfigError, // Invalid configuration 26 | GCDAsyncUdpSocketBadParamError, // Invalid parameter was passed 27 | GCDAsyncUdpSocketSendTimeoutError, // A send operation timed out 28 | GCDAsyncUdpSocketClosedError, // The socket was closed 29 | GCDAsyncUdpSocketOtherError, // Description provided in userInfo 30 | }; 31 | 32 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 33 | #pragma mark - 34 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 35 | 36 | @class GCDAsyncUdpSocket; 37 | 38 | @protocol GCDAsyncUdpSocketDelegate 39 | @optional 40 | 41 | /** 42 | * By design, UDP is a connectionless protocol, and connecting is not needed. 43 | * However, you may optionally choose to connect to a particular host for reasons 44 | * outlined in the documentation for the various connect methods listed above. 45 | * 46 | * This method is called if one of the connect methods are invoked, and the connection is successful. 47 | **/ 48 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address; 49 | 50 | /** 51 | * By design, UDP is a connectionless protocol, and connecting is not needed. 52 | * However, you may optionally choose to connect to a particular host for reasons 53 | * outlined in the documentation for the various connect methods listed above. 54 | * 55 | * This method is called if one of the connect methods are invoked, and the connection fails. 56 | * This may happen, for example, if a domain name is given for the host and the domain name is unable to be resolved. 57 | **/ 58 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError * _Nullable)error; 59 | 60 | /** 61 | * Called when the datagram with the given tag has been sent. 62 | **/ 63 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didSendDataWithTag:(long)tag; 64 | 65 | /** 66 | * Called if an error occurs while trying to send a datagram. 67 | * This could be due to a timeout, or something more serious such as the data being too large to fit in a sigle packet. 68 | **/ 69 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotSendDataWithTag:(long)tag dueToError:(NSError * _Nullable)error; 70 | 71 | /** 72 | * Called when the socket has received the requested datagram. 73 | **/ 74 | - (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data 75 | fromAddress:(NSData *)address 76 | withFilterContext:(nullable id)filterContext; 77 | 78 | /** 79 | * Called when the socket is closed. 80 | **/ 81 | - (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError * _Nullable)error; 82 | 83 | @end 84 | 85 | /** 86 | * You may optionally set a receive filter for the socket. 87 | * A filter can provide several useful features: 88 | * 89 | * 1. Many times udp packets need to be parsed. 90 | * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. 91 | * The end result is a parallel socket io, datagram parsing, and packet processing. 92 | * 93 | * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. 94 | * The filter can prevent such packets from arriving at the delegate. 95 | * And because the filter can run in its own independent queue, this doesn't slow down the delegate. 96 | * 97 | * - Since the udp protocol does not guarantee delivery, udp packets may be lost. 98 | * Many protocols built atop udp thus provide various resend/re-request algorithms. 99 | * This sometimes results in duplicate packets arriving. 100 | * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. 101 | * 102 | * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. 103 | * Such packets need to be ignored. 104 | * 105 | * 3. Sometimes traffic shapers are needed to simulate real world environments. 106 | * A filter allows you to write custom code to simulate such environments. 107 | * The ability to code this yourself is especially helpful when your simulated environment 108 | * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), 109 | * or the system tools to handle this aren't available (e.g. on a mobile device). 110 | * 111 | * @param data - The packet that was received. 112 | * @param address - The address the data was received from. 113 | * See utilities section for methods to extract info from address. 114 | * @param context - Out parameter you may optionally set, which will then be passed to the delegate method. 115 | * For example, filter block can parse the data and then, 116 | * pass the parsed data to the delegate. 117 | * 118 | * @returns - YES if the received packet should be passed onto the delegate. 119 | * NO if the received packet should be discarded, and not reported to the delegete. 120 | * 121 | * Example: 122 | * 123 | * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { 124 | * 125 | * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; 126 | * 127 | * *context = response; 128 | * return (response != nil); 129 | * }; 130 | * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; 131 | * 132 | **/ 133 | typedef BOOL (^GCDAsyncUdpSocketReceiveFilterBlock)(NSData *data, NSData *address, id __nullable * __nonnull context); 134 | 135 | /** 136 | * You may optionally set a send filter for the socket. 137 | * A filter can provide several interesting possibilities: 138 | * 139 | * 1. Optional caching of resolved addresses for domain names. 140 | * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. 141 | * 142 | * 2. Reusable modules of code for bandwidth monitoring. 143 | * 144 | * 3. Sometimes traffic shapers are needed to simulate real world environments. 145 | * A filter allows you to write custom code to simulate such environments. 146 | * The ability to code this yourself is especially helpful when your simulated environment 147 | * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), 148 | * or the system tools to handle this aren't available (e.g. on a mobile device). 149 | * 150 | * @param data - The packet that was received. 151 | * @param address - The address the data was received from. 152 | * See utilities section for methods to extract info from address. 153 | * @param tag - The tag that was passed in the send method. 154 | * 155 | * @returns - YES if the packet should actually be sent over the socket. 156 | * NO if the packet should be silently dropped (not sent over the socket). 157 | * 158 | * Regardless of the return value, the delegate will be informed that the packet was successfully sent. 159 | * 160 | **/ 161 | typedef BOOL (^GCDAsyncUdpSocketSendFilterBlock)(NSData *data, NSData *address, long tag); 162 | 163 | 164 | @interface GCDAsyncUdpSocket : NSObject 165 | 166 | /** 167 | * GCDAsyncUdpSocket uses the standard delegate paradigm, 168 | * but executes all delegate callbacks on a given delegate dispatch queue. 169 | * This allows for maximum concurrency, while at the same time providing easy thread safety. 170 | * 171 | * You MUST set a delegate AND delegate dispatch queue before attempting to 172 | * use the socket, or you will get an error. 173 | * 174 | * The socket queue is optional. 175 | * If you pass NULL, GCDAsyncSocket will automatically create its own socket queue. 176 | * If you choose to provide a socket queue, the socket queue must not be a concurrent queue, 177 | * then please see the discussion for the method markSocketQueueTargetQueue. 178 | * 179 | * The delegate queue and socket queue can optionally be the same. 180 | **/ 181 | - (instancetype)init; 182 | - (instancetype)initWithSocketQueue:(nullable dispatch_queue_t)sq; 183 | - (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq; 184 | - (instancetype)initWithDelegate:(nullable id )aDelegate delegateQueue:(nullable dispatch_queue_t)dq socketQueue:(nullable dispatch_queue_t)sq; 185 | 186 | #pragma mark Configuration 187 | 188 | - (nullable id )delegate; 189 | - (void)setDelegate:(nullable id )delegate; 190 | - (void)synchronouslySetDelegate:(nullable id )delegate; 191 | 192 | - (nullable dispatch_queue_t)delegateQueue; 193 | - (void)setDelegateQueue:(nullable dispatch_queue_t)delegateQueue; 194 | - (void)synchronouslySetDelegateQueue:(nullable dispatch_queue_t)delegateQueue; 195 | 196 | - (void)getDelegate:(id __nullable * __nullable)delegatePtr delegateQueue:(dispatch_queue_t __nullable * __nullable)delegateQueuePtr; 197 | - (void)setDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; 198 | - (void)synchronouslySetDelegate:(nullable id )delegate delegateQueue:(nullable dispatch_queue_t)delegateQueue; 199 | 200 | /** 201 | * By default, both IPv4 and IPv6 are enabled. 202 | * 203 | * This means GCDAsyncUdpSocket automatically supports both protocols, 204 | * and can send to IPv4 or IPv6 addresses, 205 | * as well as receive over IPv4 and IPv6. 206 | * 207 | * For operations that require DNS resolution, GCDAsyncUdpSocket supports both IPv4 and IPv6. 208 | * If a DNS lookup returns only IPv4 results, GCDAsyncUdpSocket will automatically use IPv4. 209 | * If a DNS lookup returns only IPv6 results, GCDAsyncUdpSocket will automatically use IPv6. 210 | * If a DNS lookup returns both IPv4 and IPv6 results, then the protocol used depends on the configured preference. 211 | * If IPv4 is preferred, then IPv4 is used. 212 | * If IPv6 is preferred, then IPv6 is used. 213 | * If neutral, then the first IP version in the resolved array will be used. 214 | * 215 | * Starting with Mac OS X 10.7 Lion and iOS 5, the default IP preference is neutral. 216 | * On prior systems the default IP preference is IPv4. 217 | **/ 218 | - (BOOL)isIPv4Enabled; 219 | - (void)setIPv4Enabled:(BOOL)flag; 220 | 221 | - (BOOL)isIPv6Enabled; 222 | - (void)setIPv6Enabled:(BOOL)flag; 223 | 224 | - (BOOL)isIPv4Preferred; 225 | - (BOOL)isIPv6Preferred; 226 | - (BOOL)isIPVersionNeutral; 227 | 228 | - (void)setPreferIPv4; 229 | - (void)setPreferIPv6; 230 | - (void)setIPVersionNeutral; 231 | 232 | /** 233 | * Gets/Sets the maximum size of the buffer that will be allocated for receive operations. 234 | * The default maximum size is 65535 bytes. 235 | * 236 | * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535. 237 | * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295. 238 | * 239 | * Since the OS/GCD notifies us of the size of each received UDP packet, 240 | * the actual allocated buffer size for each packet is exact. 241 | * And in practice the size of UDP packets is generally much smaller than the max. 242 | * Indeed most protocols will send and receive packets of only a few bytes, 243 | * or will set a limit on the size of packets to prevent fragmentation in the IP layer. 244 | * 245 | * If you set the buffer size too small, the sockets API in the OS will silently discard 246 | * any extra data, and you will not be notified of the error. 247 | **/ 248 | - (uint16_t)maxReceiveIPv4BufferSize; 249 | - (void)setMaxReceiveIPv4BufferSize:(uint16_t)max; 250 | 251 | - (uint32_t)maxReceiveIPv6BufferSize; 252 | - (void)setMaxReceiveIPv6BufferSize:(uint32_t)max; 253 | 254 | /** 255 | * Gets/Sets the maximum size of the buffer that will be allocated for send operations. 256 | * The default maximum size is 65535 bytes. 257 | * 258 | * Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be 259 | * fragmented, and that’s both expensive and risky (if one fragment goes missing, the 260 | * entire datagram is lost). You are much better off sending a large number of smaller 261 | * UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation. 262 | * 263 | * You must set it before the sockt is created otherwise it won't work. 264 | * 265 | **/ 266 | - (uint16_t)maxSendBufferSize; 267 | - (void)setMaxSendBufferSize:(uint16_t)max; 268 | 269 | /** 270 | * User data allows you to associate arbitrary information with the socket. 271 | * This data is not used internally in any way. 272 | **/ 273 | - (nullable id)userData; 274 | - (void)setUserData:(nullable id)arbitraryUserData; 275 | 276 | #pragma mark Diagnostics 277 | 278 | /** 279 | * Returns the local address info for the socket. 280 | * 281 | * The localAddress method returns a sockaddr structure wrapped in a NSData object. 282 | * The localHost method returns the human readable IP address as a string. 283 | * 284 | * Note: Address info may not be available until after the socket has been binded, connected 285 | * or until after data has been sent. 286 | **/ 287 | - (nullable NSData *)localAddress; 288 | - (nullable NSString *)localHost; 289 | - (uint16_t)localPort; 290 | 291 | - (nullable NSData *)localAddress_IPv4; 292 | - (nullable NSString *)localHost_IPv4; 293 | - (uint16_t)localPort_IPv4; 294 | 295 | - (nullable NSData *)localAddress_IPv6; 296 | - (nullable NSString *)localHost_IPv6; 297 | - (uint16_t)localPort_IPv6; 298 | 299 | /** 300 | * Returns the remote address info for the socket. 301 | * 302 | * The connectedAddress method returns a sockaddr structure wrapped in a NSData object. 303 | * The connectedHost method returns the human readable IP address as a string. 304 | * 305 | * Note: Since UDP is connectionless by design, connected address info 306 | * will not be available unless the socket is explicitly connected to a remote host/port. 307 | * If the socket is not connected, these methods will return nil / 0. 308 | **/ 309 | - (nullable NSData *)connectedAddress; 310 | - (nullable NSString *)connectedHost; 311 | - (uint16_t)connectedPort; 312 | 313 | /** 314 | * Returns whether or not this socket has been connected to a single host. 315 | * By design, UDP is a connectionless protocol, and connecting is not needed. 316 | * If connected, the socket will only be able to send/receive data to/from the connected host. 317 | **/ 318 | - (BOOL)isConnected; 319 | 320 | /** 321 | * Returns whether or not this socket has been closed. 322 | * The only way a socket can be closed is if you explicitly call one of the close methods. 323 | **/ 324 | - (BOOL)isClosed; 325 | 326 | /** 327 | * Returns whether or not this socket is IPv4. 328 | * 329 | * By default this will be true, unless: 330 | * - IPv4 is disabled (via setIPv4Enabled:) 331 | * - The socket is explicitly bound to an IPv6 address 332 | * - The socket is connected to an IPv6 address 333 | **/ 334 | - (BOOL)isIPv4; 335 | 336 | /** 337 | * Returns whether or not this socket is IPv6. 338 | * 339 | * By default this will be true, unless: 340 | * - IPv6 is disabled (via setIPv6Enabled:) 341 | * - The socket is explicitly bound to an IPv4 address 342 | * _ The socket is connected to an IPv4 address 343 | * 344 | * This method will also return false on platforms that do not support IPv6. 345 | * Note: The iPhone does not currently support IPv6. 346 | **/ 347 | - (BOOL)isIPv6; 348 | 349 | #pragma mark Binding 350 | 351 | /** 352 | * Binds the UDP socket to the given port. 353 | * Binding should be done for server sockets that receive data prior to sending it. 354 | * Client sockets can skip binding, 355 | * as the OS will automatically assign the socket an available port when it starts sending data. 356 | * 357 | * You may optionally pass a port number of zero to immediately bind the socket, 358 | * yet still allow the OS to automatically assign an available port. 359 | * 360 | * You cannot bind a socket after its been connected. 361 | * You can only bind a socket once. 362 | * You can still connect a socket (if desired) after binding. 363 | * 364 | * On success, returns YES. 365 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. 366 | **/ 367 | - (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr; 368 | 369 | /** 370 | * Binds the UDP socket to the given port and optional interface. 371 | * Binding should be done for server sockets that receive data prior to sending it. 372 | * Client sockets can skip binding, 373 | * as the OS will automatically assign the socket an available port when it starts sending data. 374 | * 375 | * You may optionally pass a port number of zero to immediately bind the socket, 376 | * yet still allow the OS to automatically assign an available port. 377 | * 378 | * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). 379 | * You may also use the special strings "localhost" or "loopback" to specify that 380 | * the socket only accept packets from the local machine. 381 | * 382 | * You cannot bind a socket after its been connected. 383 | * You can only bind a socket once. 384 | * You can still connect a socket (if desired) after binding. 385 | * 386 | * On success, returns YES. 387 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. 388 | **/ 389 | - (BOOL)bindToPort:(uint16_t)port interface:(nullable NSString *)interface error:(NSError **)errPtr; 390 | 391 | /** 392 | * Binds the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. 393 | * 394 | * If you have an existing struct sockaddr you can convert it to a NSData object like so: 395 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; 396 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; 397 | * 398 | * Binding should be done for server sockets that receive data prior to sending it. 399 | * Client sockets can skip binding, 400 | * as the OS will automatically assign the socket an available port when it starts sending data. 401 | * 402 | * You cannot bind a socket after its been connected. 403 | * You can only bind a socket once. 404 | * You can still connect a socket (if desired) after binding. 405 | * 406 | * On success, returns YES. 407 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass NULL for errPtr. 408 | **/ 409 | - (BOOL)bindToAddress:(NSData *)localAddr error:(NSError **)errPtr; 410 | 411 | #pragma mark Connecting 412 | 413 | /** 414 | * Connects the UDP socket to the given host and port. 415 | * By design, UDP is a connectionless protocol, and connecting is not needed. 416 | * 417 | * Choosing to connect to a specific host/port has the following effect: 418 | * - You will only be able to send data to the connected host/port. 419 | * - You will only be able to receive data from the connected host/port. 420 | * - You will receive ICMP messages that come from the connected host/port, such as "connection refused". 421 | * 422 | * The actual process of connecting a UDP socket does not result in any communication on the socket. 423 | * It simply changes the internal state of the socket. 424 | * 425 | * You cannot bind a socket after it has been connected. 426 | * You can only connect a socket once. 427 | * 428 | * The host may be a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). 429 | * 430 | * This method is asynchronous as it requires a DNS lookup to resolve the given host name. 431 | * If an obvious error is detected, this method immediately returns NO and sets errPtr. 432 | * If you don't care about the error, you can pass nil for errPtr. 433 | * Otherwise, this method returns YES and begins the asynchronous connection process. 434 | * The result of the asynchronous connection process will be reported via the delegate methods. 435 | **/ 436 | - (BOOL)connectToHost:(NSString *)host onPort:(uint16_t)port error:(NSError **)errPtr; 437 | 438 | /** 439 | * Connects the UDP socket to the given address, specified as a sockaddr structure wrapped in a NSData object. 440 | * 441 | * If you have an existing struct sockaddr you can convert it to a NSData object like so: 442 | * struct sockaddr sa -> NSData *dsa = [NSData dataWithBytes:&remoteAddr length:remoteAddr.sa_len]; 443 | * struct sockaddr *sa -> NSData *dsa = [NSData dataWithBytes:remoteAddr length:remoteAddr->sa_len]; 444 | * 445 | * By design, UDP is a connectionless protocol, and connecting is not needed. 446 | * 447 | * Choosing to connect to a specific address has the following effect: 448 | * - You will only be able to send data to the connected address. 449 | * - You will only be able to receive data from the connected address. 450 | * - You will receive ICMP messages that come from the connected address, such as "connection refused". 451 | * 452 | * Connecting a UDP socket does not result in any communication on the socket. 453 | * It simply changes the internal state of the socket. 454 | * 455 | * You cannot bind a socket after its been connected. 456 | * You can only connect a socket once. 457 | * 458 | * On success, returns YES. 459 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 460 | * 461 | * Note: Unlike the connectToHost:onPort:error: method, this method does not require a DNS lookup. 462 | * Thus when this method returns, the connection has either failed or fully completed. 463 | * In other words, this method is synchronous, unlike the asynchronous connectToHost::: method. 464 | * However, for compatibility and simplification of delegate code, if this method returns YES 465 | * then the corresponding delegate method (udpSocket:didConnectToHost:port:) is still invoked. 466 | **/ 467 | - (BOOL)connectToAddress:(NSData *)remoteAddr error:(NSError **)errPtr; 468 | 469 | #pragma mark Multicast 470 | 471 | /** 472 | * Join multicast group. 473 | * Group should be an IP address (eg @"225.228.0.1"). 474 | * 475 | * On success, returns YES. 476 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 477 | **/ 478 | - (BOOL)joinMulticastGroup:(NSString *)group error:(NSError **)errPtr; 479 | 480 | /** 481 | * Join multicast group. 482 | * Group should be an IP address (eg @"225.228.0.1"). 483 | * The interface may be a name (e.g. "en1" or "lo0") or the corresponding IP address (e.g. "192.168.4.35"). 484 | * 485 | * On success, returns YES. 486 | * Otherwise returns NO, and sets errPtr. If you don't care about the error, you can pass nil for errPtr. 487 | **/ 488 | - (BOOL)joinMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; 489 | 490 | - (BOOL)leaveMulticastGroup:(NSString *)group error:(NSError **)errPtr; 491 | - (BOOL)leaveMulticastGroup:(NSString *)group onInterface:(nullable NSString *)interface error:(NSError **)errPtr; 492 | 493 | #pragma mark Reuse Port 494 | 495 | /** 496 | * By default, only one socket can be bound to a given IP address + port at a time. 497 | * To enable multiple processes to simultaneously bind to the same address+port, 498 | * you need to enable this functionality in the socket. All processes that wish to 499 | * use the address+port simultaneously must all enable reuse port on the socket 500 | * bound to that port. 501 | **/ 502 | - (BOOL)enableReusePort:(BOOL)flag error:(NSError **)errPtr; 503 | 504 | #pragma mark Broadcast 505 | 506 | /** 507 | * By default, the underlying socket in the OS will not allow you to send broadcast messages. 508 | * In order to send broadcast messages, you need to enable this functionality in the socket. 509 | * 510 | * A broadcast is a UDP message to addresses like "192.168.255.255" or "255.255.255.255" that is 511 | * delivered to every host on the network. 512 | * The reason this is generally disabled by default (by the OS) is to prevent 513 | * accidental broadcast messages from flooding the network. 514 | **/ 515 | - (BOOL)enableBroadcast:(BOOL)flag error:(NSError **)errPtr; 516 | 517 | #pragma mark Sending 518 | 519 | /** 520 | * Asynchronously sends the given data, with the given timeout and tag. 521 | * 522 | * This method may only be used with a connected socket. 523 | * Recall that connecting is optional for a UDP socket. 524 | * For connected sockets, data can only be sent to the connected address. 525 | * For non-connected sockets, the remote destination is specified for each packet. 526 | * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. 527 | * 528 | * @param data 529 | * The data to send. 530 | * If data is nil or zero-length, this method does nothing. 531 | * If passing NSMutableData, please read the thread-safety notice below. 532 | * 533 | * @param timeout 534 | * The timeout for the send opeartion. 535 | * If the timeout value is negative, the send operation will not use a timeout. 536 | * 537 | * @param tag 538 | * The tag is for your convenience. 539 | * It is not sent or received over the socket in any manner what-so-ever. 540 | * It is reported back as a parameter in the udpSocket:didSendDataWithTag: 541 | * or udpSocket:didNotSendDataWithTag:dueToError: methods. 542 | * You can use it as an array index, state id, type constant, etc. 543 | * 544 | * 545 | * Thread-Safety Note: 546 | * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while 547 | * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method 548 | * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying 549 | * that this particular send operation has completed. 550 | * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. 551 | * It simply retains it for performance reasons. 552 | * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. 553 | * Copying this data adds an unwanted/unneeded overhead. 554 | * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket 555 | * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time 556 | * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. 557 | **/ 558 | - (void)sendData:(NSData *)data withTimeout:(NSTimeInterval)timeout tag:(long)tag; 559 | 560 | /** 561 | * Asynchronously sends the given data, with the given timeout and tag, to the given host and port. 562 | * 563 | * This method cannot be used with a connected socket. 564 | * Recall that connecting is optional for a UDP socket. 565 | * For connected sockets, data can only be sent to the connected address. 566 | * For non-connected sockets, the remote destination is specified for each packet. 567 | * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. 568 | * 569 | * @param data 570 | * The data to send. 571 | * If data is nil or zero-length, this method does nothing. 572 | * If passing NSMutableData, please read the thread-safety notice below. 573 | * 574 | * @param host 575 | * The destination to send the udp packet to. 576 | * May be specified as a domain name (e.g. "deusty.com") or an IP address string (e.g. "192.168.0.2"). 577 | * You may also use the convenience strings of "loopback" or "localhost". 578 | * 579 | * @param port 580 | * The port of the host to send to. 581 | * 582 | * @param timeout 583 | * The timeout for the send opeartion. 584 | * If the timeout value is negative, the send operation will not use a timeout. 585 | * 586 | * @param tag 587 | * The tag is for your convenience. 588 | * It is not sent or received over the socket in any manner what-so-ever. 589 | * It is reported back as a parameter in the udpSocket:didSendDataWithTag: 590 | * or udpSocket:didNotSendDataWithTag:dueToError: methods. 591 | * You can use it as an array index, state id, type constant, etc. 592 | * 593 | * 594 | * Thread-Safety Note: 595 | * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while 596 | * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method 597 | * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying 598 | * that this particular send operation has completed. 599 | * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. 600 | * It simply retains it for performance reasons. 601 | * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. 602 | * Copying this data adds an unwanted/unneeded overhead. 603 | * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket 604 | * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time 605 | * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. 606 | **/ 607 | - (void)sendData:(NSData *)data 608 | toHost:(NSString *)host 609 | port:(uint16_t)port 610 | withTimeout:(NSTimeInterval)timeout 611 | tag:(long)tag; 612 | 613 | /** 614 | * Asynchronously sends the given data, with the given timeout and tag, to the given address. 615 | * 616 | * This method cannot be used with a connected socket. 617 | * Recall that connecting is optional for a UDP socket. 618 | * For connected sockets, data can only be sent to the connected address. 619 | * For non-connected sockets, the remote destination is specified for each packet. 620 | * For more information about optionally connecting udp sockets, see the documentation for the connect methods above. 621 | * 622 | * @param data 623 | * The data to send. 624 | * If data is nil or zero-length, this method does nothing. 625 | * If passing NSMutableData, please read the thread-safety notice below. 626 | * 627 | * @param remoteAddr 628 | * The address to send the data to (specified as a sockaddr structure wrapped in a NSData object). 629 | * 630 | * @param timeout 631 | * The timeout for the send opeartion. 632 | * If the timeout value is negative, the send operation will not use a timeout. 633 | * 634 | * @param tag 635 | * The tag is for your convenience. 636 | * It is not sent or received over the socket in any manner what-so-ever. 637 | * It is reported back as a parameter in the udpSocket:didSendDataWithTag: 638 | * or udpSocket:didNotSendDataWithTag:dueToError: methods. 639 | * You can use it as an array index, state id, type constant, etc. 640 | * 641 | * 642 | * Thread-Safety Note: 643 | * If the given data parameter is mutable (NSMutableData) then you MUST NOT alter the data while 644 | * the socket is sending it. In other words, it's not safe to alter the data until after the delegate method 645 | * udpSocket:didSendDataWithTag: or udpSocket:didNotSendDataWithTag:dueToError: is invoked signifying 646 | * that this particular send operation has completed. 647 | * This is due to the fact that GCDAsyncUdpSocket does NOT copy the data. 648 | * It simply retains it for performance reasons. 649 | * Often times, if NSMutableData is passed, it is because a request/response was built up in memory. 650 | * Copying this data adds an unwanted/unneeded overhead. 651 | * If you need to write data from an immutable buffer, and you need to alter the buffer before the socket 652 | * completes sending the bytes (which is NOT immediately after this method returns, but rather at a later time 653 | * when the delegate method notifies you), then you should first copy the bytes, and pass the copy to this method. 654 | **/ 655 | - (void)sendData:(NSData *)data toAddress:(NSData *)remoteAddr withTimeout:(NSTimeInterval)timeout tag:(long)tag; 656 | 657 | /** 658 | * You may optionally set a send filter for the socket. 659 | * A filter can provide several interesting possibilities: 660 | * 661 | * 1. Optional caching of resolved addresses for domain names. 662 | * The cache could later be consulted, resulting in fewer system calls to getaddrinfo. 663 | * 664 | * 2. Reusable modules of code for bandwidth monitoring. 665 | * 666 | * 3. Sometimes traffic shapers are needed to simulate real world environments. 667 | * A filter allows you to write custom code to simulate such environments. 668 | * The ability to code this yourself is especially helpful when your simulated environment 669 | * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), 670 | * or the system tools to handle this aren't available (e.g. on a mobile device). 671 | * 672 | * For more information about GCDAsyncUdpSocketSendFilterBlock, see the documentation for its typedef. 673 | * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. 674 | * 675 | * Note: This method invokes setSendFilter:withQueue:isAsynchronous: (documented below), 676 | * passing YES for the isAsynchronous parameter. 677 | **/ 678 | - (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; 679 | 680 | /** 681 | * The receive filter can be run via dispatch_async or dispatch_sync. 682 | * Most typical situations call for asynchronous operation. 683 | * 684 | * However, there are a few situations in which synchronous operation is preferred. 685 | * Such is the case when the filter is extremely minimal and fast. 686 | * This is because dispatch_sync is faster than dispatch_async. 687 | * 688 | * If you choose synchronous operation, be aware of possible deadlock conditions. 689 | * Since the socket queue is executing your block via dispatch_sync, 690 | * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. 691 | * For example, you can't query properties on the socket. 692 | **/ 693 | - (void)setSendFilter:(nullable GCDAsyncUdpSocketSendFilterBlock)filterBlock 694 | withQueue:(nullable dispatch_queue_t)filterQueue 695 | isAsynchronous:(BOOL)isAsynchronous; 696 | 697 | #pragma mark Receiving 698 | 699 | /** 700 | * There are two modes of operation for receiving packets: one-at-a-time & continuous. 701 | * 702 | * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. 703 | * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, 704 | * where your state machine may not always be ready to process incoming packets. 705 | * 706 | * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. 707 | * Receiving packets continuously is better suited to real-time streaming applications. 708 | * 709 | * You may switch back and forth between one-at-a-time mode and continuous mode. 710 | * If the socket is currently in continuous mode, calling this method will switch it to one-at-a-time mode. 711 | * 712 | * When a packet is received (and not filtered by the optional receive filter), 713 | * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. 714 | * 715 | * If the socket is able to begin receiving packets, this method returns YES. 716 | * Otherwise it returns NO, and sets the errPtr with appropriate error information. 717 | * 718 | * An example error: 719 | * You created a udp socket to act as a server, and immediately called receive. 720 | * You forgot to first bind the socket to a port number, and received a error with a message like: 721 | * "Must bind socket before you can receive data." 722 | **/ 723 | - (BOOL)receiveOnce:(NSError **)errPtr; 724 | 725 | /** 726 | * There are two modes of operation for receiving packets: one-at-a-time & continuous. 727 | * 728 | * In one-at-a-time mode, you call receiveOnce everytime your delegate is ready to process an incoming udp packet. 729 | * Receiving packets one-at-a-time may be better suited for implementing certain state machine code, 730 | * where your state machine may not always be ready to process incoming packets. 731 | * 732 | * In continuous mode, the delegate is invoked immediately everytime incoming udp packets are received. 733 | * Receiving packets continuously is better suited to real-time streaming applications. 734 | * 735 | * You may switch back and forth between one-at-a-time mode and continuous mode. 736 | * If the socket is currently in one-at-a-time mode, calling this method will switch it to continuous mode. 737 | * 738 | * For every received packet (not filtered by the optional receive filter), 739 | * the delegate method (udpSocket:didReceiveData:fromAddress:withFilterContext:) is invoked. 740 | * 741 | * If the socket is able to begin receiving packets, this method returns YES. 742 | * Otherwise it returns NO, and sets the errPtr with appropriate error information. 743 | * 744 | * An example error: 745 | * You created a udp socket to act as a server, and immediately called receive. 746 | * You forgot to first bind the socket to a port number, and received a error with a message like: 747 | * "Must bind socket before you can receive data." 748 | **/ 749 | - (BOOL)beginReceiving:(NSError **)errPtr; 750 | 751 | /** 752 | * If the socket is currently receiving (beginReceiving has been called), this method pauses the receiving. 753 | * That is, it won't read any more packets from the underlying OS socket until beginReceiving is called again. 754 | * 755 | * Important Note: 756 | * GCDAsyncUdpSocket may be running in parallel with your code. 757 | * That is, your delegate is likely running on a separate thread/dispatch_queue. 758 | * When you invoke this method, GCDAsyncUdpSocket may have already dispatched delegate methods to be invoked. 759 | * Thus, if those delegate methods have already been dispatch_async'd, 760 | * your didReceive delegate method may still be invoked after this method has been called. 761 | * You should be aware of this, and program defensively. 762 | **/ 763 | - (void)pauseReceiving; 764 | 765 | /** 766 | * You may optionally set a receive filter for the socket. 767 | * This receive filter may be set to run in its own queue (independent of delegate queue). 768 | * 769 | * A filter can provide several useful features. 770 | * 771 | * 1. Many times udp packets need to be parsed. 772 | * Since the filter can run in its own independent queue, you can parallelize this parsing quite easily. 773 | * The end result is a parallel socket io, datagram parsing, and packet processing. 774 | * 775 | * 2. Many times udp packets are discarded because they are duplicate/unneeded/unsolicited. 776 | * The filter can prevent such packets from arriving at the delegate. 777 | * And because the filter can run in its own independent queue, this doesn't slow down the delegate. 778 | * 779 | * - Since the udp protocol does not guarantee delivery, udp packets may be lost. 780 | * Many protocols built atop udp thus provide various resend/re-request algorithms. 781 | * This sometimes results in duplicate packets arriving. 782 | * A filter may allow you to architect the duplicate detection code to run in parallel to normal processing. 783 | * 784 | * - Since the udp socket may be connectionless, its possible for unsolicited packets to arrive. 785 | * Such packets need to be ignored. 786 | * 787 | * 3. Sometimes traffic shapers are needed to simulate real world environments. 788 | * A filter allows you to write custom code to simulate such environments. 789 | * The ability to code this yourself is especially helpful when your simulated environment 790 | * is more complicated than simple traffic shaping (e.g. simulating a cone port restricted router), 791 | * or the system tools to handle this aren't available (e.g. on a mobile device). 792 | * 793 | * Example: 794 | * 795 | * GCDAsyncUdpSocketReceiveFilterBlock filter = ^BOOL (NSData *data, NSData *address, id *context) { 796 | * 797 | * MyProtocolMessage *msg = [MyProtocol parseMessage:data]; 798 | * 799 | * *context = response; 800 | * return (response != nil); 801 | * }; 802 | * [udpSocket setReceiveFilter:filter withQueue:myParsingQueue]; 803 | * 804 | * For more information about GCDAsyncUdpSocketReceiveFilterBlock, see the documentation for its typedef. 805 | * To remove a previously set filter, invoke this method and pass a nil filterBlock and NULL filterQueue. 806 | * 807 | * Note: This method invokes setReceiveFilter:withQueue:isAsynchronous: (documented below), 808 | * passing YES for the isAsynchronous parameter. 809 | **/ 810 | - (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock withQueue:(nullable dispatch_queue_t)filterQueue; 811 | 812 | /** 813 | * The receive filter can be run via dispatch_async or dispatch_sync. 814 | * Most typical situations call for asynchronous operation. 815 | * 816 | * However, there are a few situations in which synchronous operation is preferred. 817 | * Such is the case when the filter is extremely minimal and fast. 818 | * This is because dispatch_sync is faster than dispatch_async. 819 | * 820 | * If you choose synchronous operation, be aware of possible deadlock conditions. 821 | * Since the socket queue is executing your block via dispatch_sync, 822 | * then you cannot perform any tasks which may invoke dispatch_sync on the socket queue. 823 | * For example, you can't query properties on the socket. 824 | **/ 825 | - (void)setReceiveFilter:(nullable GCDAsyncUdpSocketReceiveFilterBlock)filterBlock 826 | withQueue:(nullable dispatch_queue_t)filterQueue 827 | isAsynchronous:(BOOL)isAsynchronous; 828 | 829 | #pragma mark Closing 830 | 831 | /** 832 | * Immediately closes the underlying socket. 833 | * Any pending send operations are discarded. 834 | * 835 | * The GCDAsyncUdpSocket instance may optionally be used again. 836 | * (it will setup/configure/use another unnderlying BSD socket). 837 | **/ 838 | - (void)close; 839 | 840 | /** 841 | * Closes the underlying socket after all pending send operations have been sent. 842 | * 843 | * The GCDAsyncUdpSocket instance may optionally be used again. 844 | * (it will setup/configure/use another unnderlying BSD socket). 845 | **/ 846 | - (void)closeAfterSending; 847 | 848 | #pragma mark Advanced 849 | /** 850 | * GCDAsyncSocket maintains thread safety by using an internal serial dispatch_queue. 851 | * In most cases, the instance creates this queue itself. 852 | * However, to allow for maximum flexibility, the internal queue may be passed in the init method. 853 | * This allows for some advanced options such as controlling socket priority via target queues. 854 | * However, when one begins to use target queues like this, they open the door to some specific deadlock issues. 855 | * 856 | * For example, imagine there are 2 queues: 857 | * dispatch_queue_t socketQueue; 858 | * dispatch_queue_t socketTargetQueue; 859 | * 860 | * If you do this (pseudo-code): 861 | * socketQueue.targetQueue = socketTargetQueue; 862 | * 863 | * Then all socketQueue operations will actually get run on the given socketTargetQueue. 864 | * This is fine and works great in most situations. 865 | * But if you run code directly from within the socketTargetQueue that accesses the socket, 866 | * you could potentially get deadlock. Imagine the following code: 867 | * 868 | * - (BOOL)socketHasSomething 869 | * { 870 | * __block BOOL result = NO; 871 | * dispatch_block_t block = ^{ 872 | * result = [self someInternalMethodToBeRunOnlyOnSocketQueue]; 873 | * } 874 | * if (is_executing_on_queue(socketQueue)) 875 | * block(); 876 | * else 877 | * dispatch_sync(socketQueue, block); 878 | * 879 | * return result; 880 | * } 881 | * 882 | * What happens if you call this method from the socketTargetQueue? The result is deadlock. 883 | * This is because the GCD API offers no mechanism to discover a queue's targetQueue. 884 | * Thus we have no idea if our socketQueue is configured with a targetQueue. 885 | * If we had this information, we could easily avoid deadlock. 886 | * But, since these API's are missing or unfeasible, you'll have to explicitly set it. 887 | * 888 | * IF you pass a socketQueue via the init method, 889 | * AND you've configured the passed socketQueue with a targetQueue, 890 | * THEN you should pass the end queue in the target hierarchy. 891 | * 892 | * For example, consider the following queue hierarchy: 893 | * socketQueue -> ipQueue -> moduleQueue 894 | * 895 | * This example demonstrates priority shaping within some server. 896 | * All incoming client connections from the same IP address are executed on the same target queue. 897 | * And all connections for a particular module are executed on the same target queue. 898 | * Thus, the priority of all networking for the entire module can be changed on the fly. 899 | * Additionally, networking traffic from a single IP cannot monopolize the module. 900 | * 901 | * Here's how you would accomplish something like that: 902 | * - (dispatch_queue_t)newSocketQueueForConnectionFromAddress:(NSData *)address onSocket:(GCDAsyncSocket *)sock 903 | * { 904 | * dispatch_queue_t socketQueue = dispatch_queue_create("", NULL); 905 | * dispatch_queue_t ipQueue = [self ipQueueForAddress:address]; 906 | * 907 | * dispatch_set_target_queue(socketQueue, ipQueue); 908 | * dispatch_set_target_queue(iqQueue, moduleQueue); 909 | * 910 | * return socketQueue; 911 | * } 912 | * - (void)socket:(GCDAsyncSocket *)sock didAcceptNewSocket:(GCDAsyncSocket *)newSocket 913 | * { 914 | * [clientConnections addObject:newSocket]; 915 | * [newSocket markSocketQueueTargetQueue:moduleQueue]; 916 | * } 917 | * 918 | * Note: This workaround is ONLY needed if you intend to execute code directly on the ipQueue or moduleQueue. 919 | * This is often NOT the case, as such queues are used solely for execution shaping. 920 | **/ 921 | - (void)markSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreConfiguredTargetQueue; 922 | - (void)unmarkSocketQueueTargetQueue:(dispatch_queue_t)socketQueuesPreviouslyConfiguredTargetQueue; 923 | 924 | /** 925 | * It's not thread-safe to access certain variables from outside the socket's internal queue. 926 | * 927 | * For example, the socket file descriptor. 928 | * File descriptors are simply integers which reference an index in the per-process file table. 929 | * However, when one requests a new file descriptor (by opening a file or socket), 930 | * the file descriptor returned is guaranteed to be the lowest numbered unused descriptor. 931 | * So if we're not careful, the following could be possible: 932 | * 933 | * - Thread A invokes a method which returns the socket's file descriptor. 934 | * - The socket is closed via the socket's internal queue on thread B. 935 | * - Thread C opens a file, and subsequently receives the file descriptor that was previously the socket's FD. 936 | * - Thread A is now accessing/altering the file instead of the socket. 937 | * 938 | * In addition to this, other variables are not actually objects, 939 | * and thus cannot be retained/released or even autoreleased. 940 | * An example is the sslContext, of type SSLContextRef, which is actually a malloc'd struct. 941 | * 942 | * Although there are internal variables that make it difficult to maintain thread-safety, 943 | * it is important to provide access to these variables 944 | * to ensure this class can be used in a wide array of environments. 945 | * This method helps to accomplish this by invoking the current block on the socket's internal queue. 946 | * The methods below can be invoked from within the block to access 947 | * those generally thread-unsafe internal variables in a thread-safe manner. 948 | * The given block will be invoked synchronously on the socket's internal queue. 949 | * 950 | * If you save references to any protected variables and use them outside the block, you do so at your own peril. 951 | **/ 952 | - (void)performBlock:(dispatch_block_t)block; 953 | 954 | /** 955 | * These methods are only available from within the context of a performBlock: invocation. 956 | * See the documentation for the performBlock: method above. 957 | * 958 | * Provides access to the socket's file descriptor(s). 959 | * If the socket isn't connected, or explicity bound to a particular interface, 960 | * it might actually have multiple internal socket file descriptors - one for IPv4 and one for IPv6. 961 | **/ 962 | - (int)socketFD; 963 | - (int)socket4FD; 964 | - (int)socket6FD; 965 | 966 | #if TARGET_OS_IPHONE 967 | 968 | /** 969 | * These methods are only available from within the context of a performBlock: invocation. 970 | * See the documentation for the performBlock: method above. 971 | * 972 | * Returns (creating if necessary) a CFReadStream/CFWriteStream for the internal socket. 973 | * 974 | * Generally GCDAsyncUdpSocket doesn't use CFStream. (It uses the faster GCD API's.) 975 | * However, if you need one for any reason, 976 | * these methods are a convenient way to get access to a safe instance of one. 977 | **/ 978 | - (nullable CFReadStreamRef)readStream; 979 | - (nullable CFWriteStreamRef)writeStream; 980 | 981 | /** 982 | * This method is only available from within the context of a performBlock: invocation. 983 | * See the documentation for the performBlock: method above. 984 | * 985 | * Configures the socket to allow it to operate when the iOS application has been backgrounded. 986 | * In other words, this method creates a read & write stream, and invokes: 987 | * 988 | * CFReadStreamSetProperty(readStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); 989 | * CFWriteStreamSetProperty(writeStream, kCFStreamNetworkServiceType, kCFStreamNetworkServiceTypeVoIP); 990 | * 991 | * Returns YES if successful, NO otherwise. 992 | * 993 | * Example usage: 994 | * 995 | * [asyncUdpSocket performBlock:^{ 996 | * [asyncUdpSocket enableBackgroundingOnSocket]; 997 | * }]; 998 | * 999 | * 1000 | * NOTE : Apple doesn't currently support backgrounding UDP sockets. (Only TCP for now). 1001 | **/ 1002 | //- (BOOL)enableBackgroundingOnSockets; 1003 | 1004 | #endif 1005 | 1006 | #pragma mark Utilities 1007 | 1008 | /** 1009 | * Extracting host/port/family information from raw address data. 1010 | **/ 1011 | 1012 | + (nullable NSString *)hostFromAddress:(NSData *)address; 1013 | + (uint16_t)portFromAddress:(NSData *)address; 1014 | + (int)familyFromAddress:(NSData *)address; 1015 | 1016 | + (BOOL)isIPv4Address:(NSData *)address; 1017 | + (BOOL)isIPv6Address:(NSData *)address; 1018 | 1019 | + (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr fromAddress:(NSData *)address; 1020 | + (BOOL)getHost:(NSString * __nullable * __nullable)hostPtr port:(uint16_t * __nullable)portPtr family:(int * __nullable)afPtr fromAddress:(NSData *)address; 1021 | 1022 | @end 1023 | 1024 | NS_ASSUME_NONNULL_END 1025 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010-2011 Figure 53 LLC, http://figure53.com 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /MetatoneNetworkManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // MetatoneNetworkManager.h 3 | // Metatone 4 | // 5 | // Created by Charles Martin on 10/04/13. 6 | // Copyright (c) 2013 Charles Martin. All rights reserved. 7 | // Updated Version to work with F53OSC. 8 | // 9 | 10 | #import 11 | #import "F53OSC.h" 12 | #import "SRWebSocket.h" 13 | 14 | 15 | // IP Address method 16 | #import 17 | #import 18 | 19 | @protocol MetatoneNetworkManagerDelegate 20 | -(void) searchingForLoggingServer; 21 | -(void) loggingServerFoundWithAddress: (NSString *) address andPort: (int) port andHostname:(NSString *) hostname; 22 | -(void) stoppedSearchingForLoggingServer; 23 | -(void) didReceiveMetatoneMessageFrom:(NSString*)device withName:(NSString*)name andState:(NSString*)state; 24 | -(void) didReceiveGestureMessageFor:(NSString*)device withClass:(NSString*)class; 25 | -(void) didReceiveEnsembleState:(NSString*)state withSpread:(NSNumber*)spread withRatio:(NSNumber*)ratio; 26 | -(void) didReceiveEnsembleEvent:(NSString*)event forDevice:(NSString*)device withMeasure:(NSNumber*)measure; 27 | -(void)didReceivePerformanceStartEvent:(NSString *)event forDevice:(NSString *)device withType:(NSNumber *)type andComposition:(NSNumber *)composition; 28 | -(void)didReceivePerformanceEndEvent:(NSString *)event forDevice:(NSString *)device; 29 | -(void) metatoneClientFoundWithAddress: (NSString *) address andPort: (int) port andHostname:(NSString *) hostname; 30 | -(void) metatoneClientRemovedwithAddress: (NSString *) address andPort: (int) port andHostname:(NSString *) hostname; 31 | -(void) didReceiveGesturePlayMessageFor:(NSString*)device withClass:(NSString*)cla; 32 | -(void) didReceiveTouchPlayMessageFor:(NSString*)device X:(NSNumber*)x Y:(NSNumber*)y vel:(NSNumber*)vel; 33 | @end 34 | 35 | @interface MetatoneNetworkManager : NSObject 36 | 37 | @property (strong,nonatomic) F53OSCClient *oscClient; 38 | @property (strong,nonatomic) F53OSCServer *oscServer; 39 | @property (strong,nonatomic) SRWebSocket *classifierWebSocket; 40 | @property (strong, nonatomic) NSString *loggingIPAddress; 41 | @property (nonatomic) NSInteger loggingPort; 42 | @property (strong, nonatomic) NSString *loggingHostname; 43 | @property (strong, nonatomic) NSString *deviceID; 44 | @property (strong,nonatomic) NSString *appID; 45 | @property (strong, nonatomic) NSString *localIPAddress; 46 | /// Hostname for the web classifier server. 47 | @property (strong, nonatomic) NSString *webClassifierHostname; 48 | /// Port for the web classifier server. 49 | @property (nonatomic) int webClassifierPort; 50 | @property (strong, nonatomic) NSNetServiceBrowser *oscLoggerServiceBrowser; 51 | @property (strong, nonatomic) NSNetServiceBrowser *metatoneServiceBrowser; 52 | @property (strong, nonatomic) NSNetServiceBrowser *metatoneWebClassifierBrowser; 53 | @property (strong, nonatomic) NSNetService *oscLoggerService; 54 | @property (strong, nonatomic) NSNetService *metatoneNetService; 55 | @property (strong, nonatomic) NSNetService *metatoneWebClassifierNetService; 56 | @property (strong, nonatomic) NSMutableArray *remoteMetatoneIPAddresses; 57 | @property (strong, nonatomic) NSMutableArray *remoteMetatoneNetServices; 58 | @property (nonatomic) bool oscLogging; 59 | @property (nonatomic) bool connectToWebClassifier; 60 | @property (nonatomic) bool connectToLocalClassifier; 61 | @property (nonatomic) bool connectToLocalWebSocket; 62 | /// Records if connected to a local performance server 63 | @property (nonatomic) bool connectedToLocalPerformanceServer; 64 | /// Records if connected to a remote server. 65 | @property (nonatomic) int connectedToServer; 66 | /// Delegate for triggering events and receiving data. 67 | @property (weak,nonatomic) id delegate; 68 | 69 | 70 | + (NSString *)getIPAddress; 71 | + (NSString *)getLocalBroadcastAddress; 72 | 73 | /*! Designated Initialiser. */ 74 | - (MetatoneNetworkManager *) initWithDelegate: (id) delegate shouldOscLog: (bool) osclogging; 75 | - (MetatoneNetworkManager *) initWithDelegate: (id) delegate shouldOscLog: (bool) osclogging shouldConnectToWebClassifier: (bool) connectToWeb; 76 | /*! Stops all searches and deletes records of remote services and addresses. */ 77 | - (void)stopSearches; 78 | 79 | - (void)sendMessageWithAccelerationX:(double) X Y:(double) Y Z:(double) Z; 80 | - (void)sendMessageWithTouch:(CGPoint) point Velocity:(CGFloat) vel; 81 | - (void)sendMessageTouchEnded; 82 | - (void)sendMesssageSwitch:(NSString *)name On:(BOOL)on; 83 | - (void)sendMetatoneMessage:(NSString *)name withState:(NSString *)state; 84 | - (void)sendMetatoneMessageViaServer:(NSString *)name withState:(NSString *)state; 85 | /// Sends a message alerting the server that the current app can be remote controller "/metatone/remote" 86 | - (void)sendMessageRemoteControl; 87 | - (void)closeClassifierWebSocket; 88 | 89 | 90 | /*! Opens the websocket and attempts to connect to the web classifier. */ 91 | - (void) startConnectingToWebClassifier; 92 | /*! Disconnects from the web classifier performance and closes the websocket. */ 93 | - (void) stopConnectingToWebClassifier; 94 | /*! Returns YES if either the websocket or OSC connection is connected to a classifier, otherwise returns NO. */ 95 | - (bool) isClassifierConnected; 96 | @end 97 | -------------------------------------------------------------------------------- /MetatoneNetworkManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // MetatoneNetworkManager.m 3 | // Metatone 4 | // 5 | // Created by Charles Martin on 10/04/13. 6 | // Copyright (c) 2013 Charles Martin. All rights reserved. 7 | // Modified January 2014 to work with F53OSC 8 | // 9 | 10 | #import "MetatoneNetworkManager.h" 11 | 12 | #define USE_WEBSOCKET_CLASSIFIER @YES 13 | 14 | #define DEFAULT_PORT 51200 15 | #define DEFAULT_ADDRESS @"10.0.1.199" 16 | 17 | #define METATONE_CLASSIFIER_HOSTNAME @"metatonetransfer.com" 18 | 19 | #define METATONE_CLASSIFIER_PORT 8888 20 | #define METACLASSIFIER_SERVICE_TYPE @"_metatoneclassifier._tcp." 21 | #define METATONE_SERVICE_TYPE @"_metatoneapp._udp." 22 | #define OSCLOGGER_SERVICE_TYPE @"_osclogger._udp." 23 | 24 | #define SERVER_DISCONNECTED 0 25 | #define SERVER_CONNECTING 1 26 | #define SERVER_CONNECTED 2 27 | 28 | @implementation MetatoneNetworkManager 29 | // Designated Initialisers 30 | 31 | // This initialiser just sets web classification to NO to preserve compatibility. 32 | -(MetatoneNetworkManager *) initWithDelegate:(id)delegate shouldOscLog:(bool)osclogging { 33 | return [self initWithDelegate:delegate shouldOscLog:osclogging shouldConnectToWebClassifier:NO]; 34 | } 35 | 36 | 37 | // This initialiser sets web classification by argument. 38 | -(MetatoneNetworkManager *) initWithDelegate: (id) delegate shouldOscLog: (bool) osclogging shouldConnectToWebClassifier: (bool) connectToWeb { 39 | self = [super init]; 40 | 41 | self.delegate = delegate; 42 | // if ios: [UIDevice currentDevice].name if mac: [[NSHost currentHost] localizedName] 43 | #if TARGET_OS_IPHONE 44 | self.deviceID = [UIDevice currentDevice].name; 45 | #else 46 | self.deviceID = [[NSHost currentHost] localizedName]; 47 | #endif 48 | 49 | self.appID = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleDisplayName"]; 50 | self.oscLogging = osclogging; 51 | self.connectToWebClassifier = connectToWeb; 52 | self.loggingIPAddress = DEFAULT_ADDRESS; 53 | self.loggingPort = DEFAULT_PORT; 54 | self.localIPAddress = [MetatoneNetworkManager getIPAddress]; 55 | self.connectedToServer = SERVER_DISCONNECTED; 56 | self.connectedToLocalPerformanceServer = NO; 57 | 58 | // Setup OSC Client 59 | self.oscClient = [[F53OSCClient alloc] init]; 60 | [self.oscClient setHost:self.loggingIPAddress]; 61 | [self.oscClient setPort:self.loggingPort]; 62 | [self.oscClient connect]; 63 | 64 | // Setup OSC Server 65 | self.oscServer = [[F53OSCServer alloc] init]; 66 | [self.oscServer setDelegate:self]; 67 | [self.oscServer setPort:DEFAULT_PORT]; 68 | [self.oscServer startListening]; 69 | 70 | // Connect WebSocketClassifier 71 | // if (USE_WEBSOCKET_CLASSIFIER) [self connectWebClassifierWebSocket]; 72 | if (connectToWeb) [self connectWebClassifierWebSocket]; 73 | 74 | // register with Bonjour 75 | // if ios: [UIDevice currentDevice].name if mac: [[NSHost currentHost] localizedName] 76 | #if TARGET_OS_IPHONE 77 | self.metatoneNetService = [[NSNetService alloc] 78 | initWithDomain:@"" 79 | type:METATONE_SERVICE_TYPE 80 | name:[UIDevice currentDevice].name 81 | port:DEFAULT_PORT]; 82 | #else 83 | self.metatoneNetService = [[NSNetService alloc] 84 | initWithDomain:@"" 85 | type:METATONE_SERVICE_TYPE 86 | name:[[NSHost currentHost] localizedName] 87 | port:DEFAULT_PORT]; 88 | #endif 89 | if (self.metatoneNetService != nil) { 90 | [self.metatoneNetService setDelegate: self]; 91 | [self.metatoneNetService publishWithOptions:0]; 92 | // if ios: [UIDevice currentDevice].name if mac: [[NSHost currentHost] localizedName] 93 | 94 | // NSLog(@"NETWORK MANAGER: Metatone NetService Published - name: %@", [[NSHost currentHost] localizedName]); 95 | 96 | } 97 | 98 | 99 | if (self.oscLogging) { 100 | // try to find an OSC Logger to connect to (but only if "oscLogging" is set). 101 | NSLog(@"NETWORK MANAGER: Browsing for OSC Logger Services..."); 102 | self.oscLoggerServiceBrowser = [[NSNetServiceBrowser alloc] init]; 103 | [self.oscLoggerServiceBrowser setDelegate:self]; 104 | [self.oscLoggerServiceBrowser searchForServicesOfType:OSCLOGGER_SERVICE_TYPE 105 | inDomain:@"local."]; 106 | } 107 | 108 | // try to find Metatone Apps to connect to (always do this) 109 | NSLog(@"NETWORK MANAGER: Browsing for Metatone App Services..."); 110 | self.metatoneServiceBrowser = [[NSNetServiceBrowser alloc] init]; 111 | [self.metatoneServiceBrowser setDelegate:self]; 112 | [self.metatoneServiceBrowser searchForServicesOfType:METATONE_SERVICE_TYPE 113 | inDomain:@"local."]; 114 | 115 | // try to find Metatone Web Classifier services locally 116 | NSLog(@"NETWORK MANAGER: Browsing for Metatone Web Classifier Services..."); 117 | self.metatoneWebClassifierBrowser = [[NSNetServiceBrowser alloc] init]; 118 | [self.metatoneWebClassifierBrowser setDelegate:self]; 119 | [self.metatoneWebClassifierBrowser searchForServicesOfType:METACLASSIFIER_SERVICE_TYPE 120 | inDomain:@"local."]; 121 | return self; 122 | } 123 | 124 | 125 | 126 | 127 | 128 | #pragma mark WebSocket Life Cycle 129 | //-(void)connectClassifierWebSocket { 130 | // [self.classifierWebSocket close]; 131 | // self.classifierWebSocket.delegate = nil; 132 | // NSString* classifierUrl = [NSString stringWithFormat:@"ws://%@:%d/classifier",METATONE_CLASSIFIER_HOSTNAME,METATONE_CLASSIFIER_PORT]; 133 | // self.classifierWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:classifierUrl]]]; 134 | // [self.classifierWebSocket setDelegate:self]; 135 | // NSLog(@"NETWORK MANAGER: Opening Classifier WebSocket."); 136 | // [self.classifierWebSocket open]; 137 | // 138 | //} 139 | 140 | #pragma mark - Main Web Classifier Connection Methods. 141 | 142 | #pragma TODO these two connection methods are problematic - what if they are called when already connected? 143 | - (void) startConnectingToWebClassifier { 144 | self.connectToWebClassifier = YES; 145 | if (self.connectedToServer == SERVER_DISCONNECTED) 146 | { 147 | NSLog(@"NETWORK MANAGER: startConnectingToWebClassifier was called."); 148 | NSLog(@"NETWORK MANAGER: WebSocket.readyState is %ld",(long)self.classifierWebSocket.readyState); 149 | if (self.classifierWebSocket.readyState == SR_CLOSED || self.classifierWebSocket.readyState == SR_CONNECTING) 150 | { 151 | NSLog(@"NETWORK MANAGER: Classifier is closed, now starting to connect to WebClassifier"); 152 | [self connectWebClassifierWebSocket]; 153 | } 154 | } 155 | } 156 | 157 | 158 | - (void) stopConnectingToWebClassifier { 159 | NSLog(@"NETWORK MANAGER: stopConnectingToWebClassifier was called."); 160 | self.connectToWebClassifier = NO; 161 | if (!self.connectedToLocalPerformanceServer) [self closeClassifierWebSocket]; 162 | } 163 | 164 | - (bool) isClassifierConnected { 165 | // Check if web classifier is connected 166 | return (self.connectedToServer == SERVER_CONNECTED); 167 | } 168 | 169 | # pragma mark - internal Web Classifier Connection Methods. 170 | 171 | -(void)connectWebClassifierWebSocket { 172 | [self connectClassifierWebSocketWithHostname:METATONE_CLASSIFIER_HOSTNAME andPort:METATONE_CLASSIFIER_PORT]; 173 | } 174 | 175 | -(void)reconnectWebClassifierWebSocket { 176 | [self connectClassifierWebSocketWithHostname:self.webClassifierHostname andPort:self.webClassifierPort]; 177 | } 178 | 179 | -(void)connectClassifierWebSocketWithHostname:(NSString *)hostname andPort:(int)port { 180 | [self.classifierWebSocket close]; 181 | self.classifierWebSocket.delegate = nil; 182 | NSString* classifierUrl = [NSString stringWithFormat:@"ws://%@:%d/classifier",hostname,port]; 183 | self.classifierWebSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:classifierUrl]]]; 184 | [self.classifierWebSocket setDelegate:self]; 185 | NSLog(@"NETWORK MANAGER: Opening Classifier WebSocket: %@:%d",hostname,port); 186 | self.connectedToServer = SERVER_CONNECTING; 187 | [self.classifierWebSocket open]; 188 | self.webClassifierHostname = hostname; // set local variable to keep hostname 189 | self.webClassifierPort = port; // set local variable to keep port 190 | } 191 | 192 | -(void)closeClassifierWebSocket { 193 | NSLog(@"NETWORK MANAGER: closeClassifierWebSocket was called."); 194 | [self sendMessageOffline]; 195 | [self.classifierWebSocket close]; 196 | self.connectedToServer = SERVER_DISCONNECTED; 197 | #pragma mark TODO There really should be a "web classifier closed" callback to the delegate. 198 | [self.delegate stoppedSearchingForLoggingServer]; // TODO: change to a more appropriately named method 199 | } 200 | 201 | -(void)webSocketDidOpen:(SRWebSocket *)webSocket { 202 | NSLog(@"NETWORK MANAGER: Classifier WebSocket Opened: %@", [webSocket description]); 203 | self.connectedToServer = SERVER_CONNECTED; 204 | [self sendMessageOnline]; 205 | [self.delegate loggingServerFoundWithAddress:self.webClassifierHostname andPort:self.webClassifierPort andHostname:self.webClassifierHostname]; 206 | } 207 | 208 | -(void)webSocket:(SRWebSocket *)webSocket didCloseWithCode:(NSInteger)code reason:(NSString *)reason wasClean:(BOOL)wasClean { 209 | NSLog(@"NETWORK MANAGER: Classifier WebSocket Closed: %@, Clean: %d", reason,wasClean); 210 | self.classifierWebSocket = nil; 211 | self.connectedToServer = SERVER_DISCONNECTED; 212 | [self.delegate stoppedSearchingForLoggingServer]; // TODO: change to a more appropriately named method 213 | } 214 | 215 | -(void)webSocket:(SRWebSocket *)webSocket didFailWithError:(NSError *)error { 216 | NSLog(@"NETWORK MANAGER: Classifier WebSocket Failed: %@", [error description]); 217 | self.classifierWebSocket = nil; 218 | self.connectedToServer = SERVER_DISCONNECTED; 219 | [self.delegate stoppedSearchingForLoggingServer]; // TODO: change to a more appropriately named method 220 | } 221 | 222 | -(void)webSocket:(SRWebSocket *)webSocket didReceiveMessage:(id)message { 223 | if ([message isKindOfClass:[NSData class]]) { 224 | NSData *messageData = [NSData dataWithBytes:[(NSData *)message bytes] length:[(NSData *)message length]]; 225 | [F53OSCParser processOscData:messageData forDestination:self replyToSocket:nil]; // This should activate the "takeMessage" method 226 | } 227 | } 228 | 229 | # pragma mark TODO fix up logic about local and remote websockets. 230 | -(void)sendToWebClassifier:(F53OSCMessage *)message { 231 | if (self.classifierWebSocket.readyState == SR_OPEN) { 232 | [self.classifierWebSocket send:[message packetData]]; 233 | } else if (self.connectToWebClassifier) { 234 | NSLog(@"NETWORK MANAGER: Can't send to WebSocket - Closed."); 235 | if(USE_WEBSOCKET_CLASSIFIER) [self reconnectWebClassifierWebSocket]; 236 | } 237 | } 238 | 239 | # pragma mark Searching Lifecycle 240 | -(void) stopSearches 241 | { 242 | [self.metatoneServiceBrowser stop]; 243 | [self.oscLoggerServiceBrowser stop]; 244 | [self.remoteMetatoneIPAddresses removeAllObjects]; 245 | [self.remoteMetatoneNetServices removeAllObjects]; 246 | [self.oscClient disconnect]; 247 | } 248 | 249 | #pragma mark Instantiation 250 | -(NSMutableArray *) remoteMetatoneNetServices { 251 | if (!_remoteMetatoneNetServices) _remoteMetatoneNetServices = [[NSMutableArray alloc] init]; 252 | return _remoteMetatoneNetServices; 253 | } 254 | 255 | -(NSMutableArray *) remoteMetatoneIPAddresses { 256 | if (!_remoteMetatoneIPAddresses) _remoteMetatoneIPAddresses = [[NSMutableArray alloc] init]; 257 | return _remoteMetatoneIPAddresses; 258 | } 259 | 260 | # pragma mark NetServiceBrowserDelegate Methods 261 | 262 | -(void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didNotSearch:(NSDictionary *)errorDict { 263 | NSLog(@"NETWORK MANAGER: ERROR: Did not search for OSC Logger"); 264 | } 265 | 266 | -(void)netServiceBrowser:(NSNetServiceBrowser *)aNetServiceBrowser didFindService:(NSNetService *)aNetService moreComing:(BOOL)moreComing { 267 | 268 | if ([[aNetService type] isEqualToString:METATONE_SERVICE_TYPE]) { 269 | if ([aNetService isEqual:self.metatoneNetService]) { 270 | NSLog(@"NETWORK MANAGER: Found own metatone service - ignoring."); 271 | return; 272 | } 273 | [aNetService setDelegate:self]; 274 | [aNetService resolveWithTimeout:5.0]; 275 | [self.remoteMetatoneNetServices addObject:aNetService]; 276 | } 277 | 278 | if ([[aNetService type] isEqualToString:OSCLOGGER_SERVICE_TYPE]) { 279 | self.oscLoggerService = aNetService; 280 | [self.oscLoggerService setDelegate:self]; 281 | [self.oscLoggerService resolveWithTimeout:5.0]; 282 | //TODO: sort out case of multiple OSC Loggers. 283 | } 284 | 285 | if ([[aNetService type] isEqualToString:METACLASSIFIER_SERVICE_TYPE]) { 286 | self.metatoneWebClassifierNetService = aNetService; 287 | [self.metatoneWebClassifierNetService setDelegate:self]; 288 | [self.metatoneWebClassifierNetService resolveWithTimeout:5.0]; 289 | } 290 | } 291 | 292 | -(void)netServiceDidResolveAddress:(NSNetService *)sender { 293 | NSString* firstAddress; 294 | int firstPort; 295 | 296 | for (NSData* data in [sender addresses]) { 297 | char addressBuffer[100]; 298 | struct sockaddr_in* socketAddress = (struct sockaddr_in*) [data bytes]; 299 | int sockFamily = socketAddress->sin_family; 300 | if (sockFamily == AF_INET || sockFamily == AF_INET6) { 301 | const char* addressStr = inet_ntop(sockFamily, 302 | &(socketAddress->sin_addr), addressBuffer, 303 | sizeof(addressBuffer)); 304 | int port = ntohs(socketAddress->sin_port); 305 | if (addressStr && port) { 306 | NSLog(@"NETWORK MANAGER: Resolved service of type %@ at %s:%d - %@", 307 | [sender type], 308 | addressStr, 309 | port, 310 | sender.hostName); 311 | firstAddress = [NSString stringWithFormat:@"%s",addressStr]; 312 | firstPort = port; 313 | break; 314 | } 315 | } 316 | } 317 | 318 | if ([sender.type isEqualToString:OSCLOGGER_SERVICE_TYPE] && firstAddress && firstPort) { 319 | self.loggingHostname = sender.hostName; 320 | self.loggingIPAddress = firstAddress; 321 | self.loggingPort = firstPort; 322 | 323 | [self.delegate loggingServerFoundWithAddress:self.loggingIPAddress 324 | andPort:(int)self.loggingPort 325 | andHostname:self.loggingHostname]; 326 | [self sendMessageOnline]; 327 | NSLog(@"NETWORK MANAGER: Resolved and Connected to an OSC Logger Service."); 328 | } 329 | 330 | if ([sender.type isEqualToString:METATONE_SERVICE_TYPE] && firstAddress && firstPort) { 331 | // Save the found address. 332 | // Need to also check if address is already in the array. 333 | if (![firstAddress isEqualToString:self.localIPAddress] && ![firstAddress isEqualToString:@"127.0.0.1"]) { 334 | [self.remoteMetatoneIPAddresses addObject:@[firstAddress,[NSNumber numberWithInt:firstPort]]]; 335 | [self.delegate metatoneClientFoundWithAddress:firstAddress andPort:firstPort andHostname:sender.hostName]; 336 | NSLog(@"NETWORK MANAGER: Resolved and Connected to a MetatoneApp Service."); 337 | } 338 | } 339 | 340 | if ([sender.type isEqualToString:METACLASSIFIER_SERVICE_TYPE] && firstAddress && firstPort) { 341 | // Connect to the webclassifier. 342 | [self connectClassifierWebSocketWithHostname:sender.hostName andPort:firstPort]; 343 | self.connectedToLocalPerformanceServer = YES; 344 | } 345 | } 346 | 347 | 348 | #pragma TODO make sure the search and stop search delegate messages are working depending on server state. 349 | -(void)netServiceBrowserWillSearch:(NSNetServiceBrowser *)aNetServiceBrowser { 350 | // only run this is not currently connected to anything. 351 | NSLog(@"NETWORK MANAGER: Connection Status: %d", self.connectedToServer); 352 | if (self.connectedToServer == SERVER_DISCONNECTED) [self.delegate searchingForLoggingServer]; 353 | } 354 | 355 | -(void)netServiceBrowserDidStopSearch:(NSNetServiceBrowser *)aNetServiceBrowser { 356 | NSLog(@"NETWORK MANAGER: NetServiceBrowser stopped searching."); 357 | if (self.connectedToServer == SERVER_DISCONNECTED) [self.delegate stoppedSearchingForLoggingServer]; 358 | } 359 | 360 | 361 | # pragma mark OSC Sending Methods 362 | -(void)sendMessageWithAccelerationX:(double)x Y:(double)y Z:(double)z 363 | { 364 | NSArray *contents = @[self.deviceID, 365 | [NSNumber numberWithFloat:x], 366 | [NSNumber numberWithFloat:y], 367 | [NSNumber numberWithFloat:z]]; 368 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/acceleration" 369 | arguments:contents]; 370 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 371 | [self sendToWebClassifier:message]; // hope this works! 372 | } 373 | 374 | -(void)sendMessageWithTouch:(CGPoint)point Velocity:(CGFloat)vel 375 | { 376 | NSArray *contents = @[self.deviceID, 377 | [NSNumber numberWithFloat:point.x], 378 | [NSNumber numberWithFloat:point.y], 379 | [NSNumber numberWithFloat:vel]]; 380 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/touch" 381 | arguments:contents]; 382 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 383 | [self sendToWebClassifier:message]; 384 | } 385 | 386 | -(void)sendMesssageSwitch:(NSString *)name On:(BOOL)on 387 | { 388 | NSString *switchState = on ? @"T" : @"F"; 389 | NSArray *contents = @[self.deviceID, 390 | name, 391 | switchState]; 392 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/switch" arguments:contents]; 393 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 394 | [self sendToWebClassifier:message]; 395 | } 396 | 397 | -(void)sendMessageTouchEnded 398 | { 399 | NSArray *contents = @[self.deviceID]; 400 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/touch/ended" arguments:contents]; 401 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 402 | [self sendToWebClassifier:message]; 403 | } 404 | 405 | -(void)sendMessageOnline 406 | { 407 | NSLog(@"Constructing Online Message."); 408 | NSArray *contents = @[self.deviceID,self.appID]; 409 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/online" arguments:contents]; 410 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 411 | [self sendToWebClassifier:message]; 412 | } 413 | 414 | -(void)sendMessageRemoteControl 415 | { 416 | NSLog(@"Sending Remote Control Message."); 417 | NSArray *contents = @[self.deviceID, [self localIPAddress]]; 418 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/remote" arguments:contents]; 419 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 420 | [self sendToWebClassifier:message]; 421 | } 422 | 423 | -(void)sendMessageOffline 424 | { 425 | NSArray *contents = @[self.deviceID,self.appID]; 426 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/offline" 427 | arguments:contents]; 428 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 429 | [self sendToWebClassifier:message]; // hope this works! 430 | } 431 | 432 | 433 | -(void)sendMetatoneMessage:(NSString *)name withState:(NSString *)state { 434 | NSArray *contents = @[self.deviceID, 435 | name, 436 | state]; 437 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/app" 438 | arguments:contents]; 439 | 440 | // Log the metatone messages as well. 441 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 442 | [self sendToWebClassifier:message]; // hope this works! 443 | 444 | // Send to each metatone app on the network. 445 | for (NSArray *address in self.remoteMetatoneIPAddresses) { 446 | [self.oscClient sendPacket:message 447 | toHost:(NSString *)address[0] 448 | onPort:[((NSNumber *)address[1]) integerValue]]; 449 | } 450 | } 451 | 452 | - (void)sendMetatoneMessageViaServer:(NSString *)name withState:(NSString *)state { 453 | NSArray *contents = @[self.deviceID, 454 | name, 455 | state]; 456 | F53OSCMessage *message = [F53OSCMessage messageWithAddressPattern:@"/metatone/app" 457 | arguments:contents]; 458 | [self.oscClient sendPacket:message toHost:self.loggingIPAddress onPort:self.loggingPort]; 459 | [self sendToWebClassifier:message]; 460 | } 461 | 462 | #pragma mark OSC Receiving Methods 463 | 464 | /// Main method for receiving and parsing OSC Messages. 465 | -(void)takeMessage:(F53OSCMessage *)message { 466 | if ([message.addressPattern isEqualToString:@"/metatone/app"]) { 467 | // InterAppmessage 468 | if ([message.arguments count] == 3) { 469 | if ([message.arguments[0] isKindOfClass:[NSString class]] && 470 | [message.arguments[1] isKindOfClass:[NSString class]] && 471 | [message.arguments[2] isKindOfClass:[NSString class]]) 472 | { 473 | if (![((NSString *) message.arguments[0]) isEqualToString:self.deviceID]) { // ignore self metatone messages. 474 | [self.delegate didReceiveMetatoneMessageFrom:message.arguments[0] withName:message.arguments[1] andState:message.arguments[2]];} 475 | } 476 | } 477 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/gesture"]) { 478 | // Gesture Message 479 | if ([message.arguments count] == 2) [self.delegate didReceiveGestureMessageFor:message.arguments[0] withClass:message.arguments[1]]; 480 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/ensemble/state"]) { 481 | //Ensemble State 482 | if ([message.arguments count] == 3) [self.delegate didReceiveEnsembleState:message.arguments[0] withSpread:message.arguments[1] withRatio:message.arguments[2]]; 483 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/ensemble/event/new_idea"]) { 484 | if ([message.arguments count] == 2) [self.delegate didReceiveEnsembleEvent:@"new_idea" forDevice:message.arguments[0] withMeasure:message.arguments[1]]; 485 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/ensemble/event/solo"]) { 486 | [self.delegate didReceiveEnsembleEvent:@"solo" forDevice:message.arguments[0] withMeasure:message.arguments[1]]; 487 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/ensemble/event/parts"]) { 488 | [self.delegate didReceiveEnsembleEvent:@"parts" forDevice:message.arguments[0] withMeasure:message.arguments[1]]; 489 | } else if ([message.addressPattern isEqualToString:@"/metatone/performance/start"]) { 490 | // performance start 491 | if ([message.arguments count] == 4) [self.delegate didReceivePerformanceStartEvent:message.arguments[0] 492 | forDevice:message.arguments[1] 493 | withType:message.arguments[2] 494 | andComposition:message.arguments[3]]; 495 | } else if ([message.addressPattern isEqualToString:@"/metatone/performance/end"]) { 496 | // performance stop 497 | if ([message.arguments count] ==2) [self.delegate didReceivePerformanceEndEvent:message.arguments[0] 498 | forDevice:message.arguments[1]]; 499 | } else if ([message.addressPattern isEqualToString:@"/metatone/classifier/hello"]) { 500 | NSLog(@"NETWORK MANAGER: Connection Handshake from Server Received."); 501 | } else if ([message.addressPattern isEqualToString:@"/metatone/playback/touch"]) { 502 | // A returned touch message from the classifier -- play it! 503 | //NSLog(@"Play back a touch of some kind"); 504 | if ([message.arguments count] == 4) [self.delegate didReceiveTouchPlayMessageFor:message.arguments[0] X:message.arguments[1] Y:message.arguments[2] vel:message.arguments[3]]; 505 | //[NSNumber numberWithFloat:point.x], 506 | //[NSNumber numberWithFloat:point.y], 507 | //[NSNumber numberWithFloat:vel]]; 508 | // Message should have three arguments. 509 | } else if ([message.addressPattern isEqualToString:@"/metatone/playback/gesture"]) { 510 | // Gesture Message to be played back! 511 | if ([message.arguments count] == 2) [self.delegate didReceiveGesturePlayMessageFor:message.arguments[0] withClass:message.arguments[1]]; 512 | } else { 513 | // Received unknown message: 514 | NSLog(@"NETWORK MANAGER: Received unknown message: %@", [message description]); 515 | } 516 | } 517 | 518 | // performance start events should be of the form: 519 | // /metatone/performance/start (string) deviceID (int) type (composition) int 520 | // the type should be 521 | //#define PERFORMANCE_TYPE_LOCAL 0 522 | //#define PERFORMANCE_TYPE_REMOTE 1 523 | //#define EXPERIMENT_TYPE_BOTH 2 524 | //#define EXPERIMENT_TYPE_NONE 3 525 | //#define EXPERIMENT_TYPE_BUTTON 4 526 | //#define EXPERIMENT_TYPE_SERVER 5 527 | // 528 | // the composition is an int that corresponds to one of the available compositions, 529 | // for the experiment, the int can be random (as long as everybody has the same one). 530 | 531 | #pragma mark IP Address Methods 532 | 533 | /// Get IP Address 534 | + (NSString *)getIPAddress { 535 | NSString *address = @"error"; 536 | struct ifaddrs *interfaces = NULL; 537 | struct ifaddrs *temp_addr = NULL; 538 | int success = 0; 539 | // retrieve the current interfaces - returns 0 on success 540 | success = getifaddrs(&interfaces); 541 | if (success == 0) { 542 | // Loop through linked list of interfaces 543 | temp_addr = interfaces; 544 | while(temp_addr != NULL) { 545 | if(temp_addr->ifa_addr->sa_family == AF_INET) { 546 | // Check if interface is en0 which is the wifi connection on the iPhone 547 | if([[NSString stringWithUTF8String:temp_addr->ifa_name] isEqualToString:@"en0"]) { 548 | // Get NSString from C String 549 | address = [NSString stringWithUTF8String:inet_ntoa(((struct sockaddr_in *)temp_addr->ifa_addr)->sin_addr)]; 550 | } 551 | } 552 | temp_addr = temp_addr->ifa_next; 553 | } 554 | } 555 | // Free memory 556 | freeifaddrs(interfaces); 557 | return address; 558 | } 559 | 560 | /// Attempt to find local broadcast address. 561 | + (NSString *)getLocalBroadcastAddress { 562 | NSArray *addressComponents = [[MetatoneNetworkManager getIPAddress] componentsSeparatedByString:@"."]; 563 | NSString *address = nil; 564 | if ([addressComponents count] == 4) 565 | { 566 | address = @""; 567 | for (int i = 0; i<([addressComponents count] - 1); i++) { 568 | address = [address stringByAppendingString:addressComponents[i]]; 569 | address = [address stringByAppendingString:@"."]; 570 | } 571 | address = [address stringByAppendingString:@"255"]; 572 | } 573 | return address; 574 | } 575 | 576 | @end 577 | -------------------------------------------------------------------------------- /NSData+F53OSCBlob.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+F53OSCBlob.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | 30 | @interface NSData (F53OSCBlobAdditions) 31 | 32 | - (NSData *) oscBlobData; 33 | + (NSData *) dataWithOSCBlobBytes:(const char *)buf maxLength:(NSUInteger)maxLength length:(NSUInteger *)outLength; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NSData+F53OSCBlob.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSData+F53OSCBlob.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSData+F53OSCBlob.h" 28 | 29 | 30 | @implementation NSData (F53OSCBlobAdditions) 31 | 32 | - (NSData *) oscBlobData 33 | { 34 | // A note on the 4s: For OSC, everything is null-terminated and in multiples of 4 bytes. 35 | // If the data is already a multiple of 4 bytes, it needs to have four null bytes appended. 36 | 37 | UInt32 dataSize = (UInt32)[self length]; 38 | dataSize = 4 * ( ceil( dataSize / 4.0 ) ); 39 | dataSize = OSSwapHostToBigInt32( dataSize ); 40 | NSMutableData *newData = [NSMutableData dataWithBytes:&dataSize length:sizeof(UInt32)]; 41 | 42 | [newData appendData:self]; 43 | 44 | char zero = 0; 45 | for ( int i = ([self length] - 1) % 4; i < 3; i++ ) 46 | [newData appendBytes:&zero length:1]; 47 | 48 | return [newData copy]; 49 | } 50 | 51 | + (NSData *) dataWithOSCBlobBytes:(const char *)buf maxLength:(NSUInteger)maxLength length:(NSUInteger *)outLength; 52 | { 53 | if ( buf == NULL || maxLength == 0 ) 54 | return nil; 55 | 56 | for ( NSUInteger index = 0; index < maxLength; index++ ) 57 | { 58 | if ( buf[index] == 0 ) { 59 | // goto valid; // Found a null character within the buffer. 60 | # pragma mark TODO - does this method make sense? Fixed it and removed a goto to kill a warning. Should still work properly. 61 | UInt32 dataSize = 0; 62 | dataSize = *((UInt32 *)buf); 63 | dataSize = OSSwapBigToHostInt32( dataSize ); 64 | *outLength = dataSize; 65 | buf += 4; 66 | return [NSData dataWithBytes:buf length:dataSize]; 67 | } 68 | } 69 | return nil; // Buffer wasn't null terminated, so it's not a valid OSC blob. 70 | 71 | // UInt32 dataSize = 0; 72 | // 73 | //valid: 74 | // dataSize = *((UInt32 *)buf); 75 | // dataSize = OSSwapBigToHostInt32( dataSize ); 76 | // *outLength = dataSize; 77 | // buf += 4; 78 | // return [NSData dataWithBytes:buf length:dataSize]; 79 | } 80 | @end 81 | -------------------------------------------------------------------------------- /NSDate+F53OSCTimeTag.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+F53OSCTimeTag.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | @class F53OSCTimeTag; 30 | 31 | @interface NSDate (F53OSCTimeTagAdditions) 32 | 33 | - (F53OSCTimeTag *) oscTimeTag; 34 | - (NSData *) oscTimeTagData; 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /NSDate+F53OSCTimeTag.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSDate+F53OSCTimeTag.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSDate+F53OSCTimeTag.h" 28 | #import "F53OSCTimeTag.h" 29 | 30 | @implementation NSDate (F53OSCTimeTagAdditions) 31 | 32 | - (F53OSCTimeTag *) oscTimeTag 33 | { 34 | return [F53OSCTimeTag timeTagWithDate:self]; 35 | } 36 | 37 | - (NSData *) oscTimeTagData 38 | { 39 | return [[self oscTimeTag] oscTimeTagData]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /NSNumber+F53OSCNumber.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSNumber+F53OSCNumber.h 3 | // 4 | // Created by Sean Dougall on 3/23/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | 30 | @interface NSNumber (F53OSCNumberAdditions) 31 | 32 | - (SInt32) oscFloatValue; 33 | - (SInt32) oscIntValue; 34 | + (NSNumber *) numberWithOSCFloatBytes:(const char *)buf maxLength:(NSUInteger)maxLength; 35 | + (NSNumber *) numberWithOSCIntBytes:(const char *)buf maxLength:(NSUInteger)maxLength; 36 | 37 | @end 38 | -------------------------------------------------------------------------------- /NSNumber+F53OSCNumber.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSNumber+F53OSCNumber.m 3 | // 4 | // Created by Sean Dougall on 3/23/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSNumber+F53OSCNumber.h" 28 | 29 | 30 | @implementation NSNumber (F53OSCNumberAdditions) 31 | 32 | - (SInt32) oscFloatValue 33 | { 34 | Float32 floatValue = [self floatValue]; 35 | SInt32 intValue = *((SInt32 *)(&floatValue)); 36 | return OSSwapHostToBigInt32( intValue ); 37 | } 38 | 39 | - (SInt32) oscIntValue 40 | { 41 | return OSSwapHostToBigInt32([self integerValue]); 42 | } 43 | 44 | + (NSNumber *) numberWithOSCFloatBytes:(const char *)buf maxLength:(NSUInteger)maxLength 45 | { 46 | if ( buf == NULL || maxLength < sizeof( SInt32 ) ) 47 | return nil; 48 | 49 | SInt32 intValue = *((SInt32 *)buf); 50 | intValue = OSSwapBigToHostInt32( intValue ); 51 | Float32 floatValue = *((Float32 *)&intValue); 52 | return [NSNumber numberWithFloat:floatValue]; 53 | } 54 | 55 | + (NSNumber *) numberWithOSCIntBytes:(const char *)buf maxLength:(NSUInteger)maxLength 56 | { 57 | if ( buf == NULL || maxLength < sizeof( SInt32 ) ) 58 | return nil; 59 | 60 | SInt32 intValue = *((SInt32 *)buf); 61 | intValue = OSSwapBigToHostInt32( intValue ); 62 | return [NSNumber numberWithInteger:intValue]; 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /NSString+F53OSCString.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+F53OSCString.h 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import 28 | 29 | 30 | @interface NSString (F53OSCStringAdditions) 31 | 32 | - (NSData *) oscStringData; 33 | + (NSString *) stringWithOSCStringBytes:(const char *)buf maxLength:(NSUInteger)maxLength length:(NSUInteger *)outLength; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /NSString+F53OSCString.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSString+F53OSCString.m 3 | // 4 | // Created by Sean Dougall on 1/17/11. 5 | // 6 | // Copyright (c) 2011-2013 Figure 53 LLC, http://figure53.com 7 | // 8 | // Permission is hereby granted, free of charge, to any person obtaining a copy 9 | // of this software and associated documentation files (the "Software"), to deal 10 | // in the Software without restriction, including without limitation the rights 11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | // copies of the Software, and to permit persons to whom the Software is 13 | // furnished to do so, subject to the following conditions: 14 | // 15 | // The above copyright notice and this permission notice shall be included in 16 | // all copies or substantial portions of the Software. 17 | // 18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 24 | // THE SOFTWARE. 25 | // 26 | 27 | #import "NSString+F53OSCString.h" 28 | 29 | 30 | @implementation NSString (F53OSCStringAdditions) 31 | 32 | - (NSData *) oscStringData 33 | { 34 | // A note on the 4s: For OSC, everything is null-terminated and in multiples of 4 bytes. 35 | // If the data is already a multiple of 4 bytes, it needs to have four null bytes appended. 36 | 37 | NSUInteger length = [self lengthOfBytesUsingEncoding:NSUTF8StringEncoding]; 38 | NSUInteger stringLength = length; 39 | const char *bytes = [self cStringUsingEncoding:NSUTF8StringEncoding]; 40 | length = 4 * ( ceil( (length + 1) / 4.0 ) ); 41 | 42 | char *string = malloc( length * sizeof( char ) ); 43 | int i; 44 | for ( i = 0; i < stringLength; i++ ) 45 | string[i] = bytes[i]; 46 | for ( ; i < length; i++ ) 47 | string[i] = 0; 48 | 49 | NSData *data = [NSData dataWithBytes:string length:length]; 50 | free( string ); 51 | return data; 52 | } 53 | 54 | /// 55 | /// An OSC string is a sequence of non-null ASCII characters followed by a null, 56 | /// followed by 0-3 additional null characters to make the total number of bits a multiple of 32. 57 | /// 58 | + (NSString *) stringWithOSCStringBytes:(const char *)buf maxLength:(NSUInteger)maxLength length:(NSUInteger *)outLength 59 | { 60 | NSString *result = nil; 61 | 62 | if ( buf == NULL || maxLength == 0 ) 63 | return nil; 64 | 65 | for ( NSUInteger index = 0; index < maxLength; index++ ) 66 | { 67 | if ( buf[index] == 0 ) 68 | goto valid; // Found a null character within the buffer. 69 | } 70 | return nil; // Buffer wasn't null terminated, so it's not a valid OSC string. 71 | 72 | valid: 73 | 74 | result = [NSString stringWithUTF8String:buf]; 75 | *outLength = 4 * ceil( ([result length] + 1) / 4.0 ); 76 | return result; 77 | } 78 | 79 | @end 80 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | 2 | # MetatoneOSC 3 | 4 | A little library for sending and receiving OSC messages in 5 | Objective-C. This is a fork of F53OSC (see below and LICENSE.txt) that I updated a little bit to 6 | work better with my own iOS projects (it's hard to find a nice OSC 7 | library in Objective-C!). 8 | 9 | # Usage 10 | 11 | Just add the source files to a project and import `F53OSC.h`. 12 | 13 | ## Sending messages 14 | 15 | F53OSCClient *oscClient = [[F53OSCClient alloc] init]; 16 | F53OSCMessage *message = 17 | [F53OSCMessage messageWithAddressPattern:@"/bla/bli/blo" 18 | arguments:@[@1,@"A string argument!",@5.82]]; 19 | [oscClient sendPacket:message toHost:@"192.168.1.14" onPort:3000];' 20 | 21 | ## Receiving messages 22 | 23 | Whatever object is receiving messages needs to be a 24 | `` and implement the `takeMessage` method. 25 | 26 | F53OSCServer *oscServer = [[F53OSCServer alloc] init]; 27 | [oscServer setPort:3000]; 28 | [oscServer setDelegate:self]; 29 | [oscServer startListening]; 30 | 31 | - (void)takeMessage:(F53OSCMessage *)message { 32 | NSString *addressPattern = message.addressPattern; 33 | NSArray *arguments = message.arguments; 34 | } 35 | 36 | ## More information 37 | 38 | I've put an example iPhone app project, [ExampleOSC](http://github.com/cpmpercussion/ExampleOSC), up on GitHub that shows how to 39 | use the library in context. 40 | 41 | # F53OSC 42 | 43 | Here's the original readme from F53OSC: 44 | 45 | Hey neat, it's a nice open source OSC library for Objective-C. 46 | 47 | From your friends at [Figure 53](http://figure53.com). 48 | 49 | For convenience, we've included a few public domain source files from [CocoaAsyncSocket](https://github.com/robbiehanson/CocoaAsyncSocket). But appropriate thanks, kudos, and curiosity about that code should be directed to [the source](https://github.com/robbiehanson/CocoaAsyncSocket). 50 | 51 | 52 | --------------------------------------------------------------------------------