Convert/cast CFReadStreamRef to NSInputStream (iOS5) - network-programming

I am trying to port my app to iOS5. I am using a TCP connection to a server via CFSockets. My problem now is the conversion (cast) from CFReadStreamRef to NSInputStream (same with write). With iOS4 I could use the toll-free bridging, but with automatic reference counting of iOS5 this isn't possible anymore. This is what I get:
error: Automatic Reference Counting Issue: Cast to 'NSInputStream *' of a non-Objective-C to an Objective-C pointer is disallowed with Automatic Reference Counting
Code:
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStringRef strRef = CFStringCreateWithCString(NULL,
[urlStr UTF8String],
NSUTF8StringEncoding);
CFStreamCreatePairWithSocketToHost(NULL,
strRef,
4444,
&readStream,
&writeStream);
NSInputStream *iStream = (NSInputStream *)readStream;
NSOutputStream *oStream = (NSOutputStream *)writeStream;
Is there another way to pipe the socket out/input into an NSStream?
Thanks for any hint!

Managing Toll-Free Bridging states very clearly that you should use something like this:
NSInputStream *iStream = objc_unretainedObject(readStream);

Related

IPv6 communication Objective c

I am developing a Mac application to communicate with printers using socket. When communicate with one printer with IPv4 ip, i got response. But when i tried using IPv6 IP with the second printer i got error
SocketStream write error [0x0]: 1 9
Error reading stream! ,Error 9: The operation couldn’t be completed.
Bad file descriptor
I use CFStreamCreatePairWithSocketToHost function for the communication. Do i need to do anything when communicate with IPv6 ip.
NSURL *myUrl = [NSURL URLWithString:#"fe80::aee2:d3ff:feff:46c7"];
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)myUrl.path, 8080, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];

No iOS socket frameworks can connect to server socket

I'm trying to use a socket framework to connect to a socket I have. I am able to connect just fine with raw socket code like:
uint portNo = 9900;
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (CFStringRef)#"123.456.789.159", portNo, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
but when I try with any framework I get an error and I'm completely stumped as to why. I've tried SocketRocket and Jetfire. Here's the code for connecting in SocketRocket that I've tried:
_webSocket.delegate = nil;
[_webSocket close];
_webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:#"wss://123.456.789.159"]];
_webSocket.delegate = self;
NSLog(#"Opening Connection...");
[_webSocket open];
I've also tried with the port number added.
_webSocket = [[SRWebSocket alloc] initWithURL:[NSURL URLWithString:#"wss://123.456.789.159:9900"]];
Also tried with http, https, ws. Still nothing.
Anybody have an idea as to why?
WebSocket is a protocol for bringing raw socket like functionality on top of HTTP. The socket that your server is exposing likely does not support the WebSocket protocol.
If you want a higher-level API for raw socket programming on iOS, CocoaAsyncSocket might be a good option for you.

iOS NSOutputStream split data after first sign

We are currently developing a TLS-server-client-application. For testing the client send a command with the content "0001" and the server sends an answer.
The client send the data via the write-method of an NSOutputStream. When the data is send first time, it arrives completely. When I'm sending it a second or third time the data is split after the first byte. The server get first "0" and than the rest "001". I have no idea why it is split.
The communication looks like: ServerConnect send to server:
0001 receive from server: OK send to server: 0 receive
from server: error send to server: 001 receive from server:
error send to server: 0 receive from server: error send
to server: 001 receive from server: error ServerDisconnect
I hope someone can help me with this.
- (void)initNetworkCommunication:(NSString*)ns_IP :(UInt32)ui_Port :(BOOL)b_ValidateCertificate
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)ns_IP, ui_Port, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:(id)self];
[outputStream setDelegate:(id)self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
NSDictionary *settings = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithBool:b_ValidateCertificate], kCFStreamSSLValidatesCertificateChain,
[NSNumber numberWithBool:YES], kCFStreamPropertyShouldCloseNativeSocket,
kCFNull,kCFStreamSSLPeerName,
kCFStreamSocketSecurityLevelSSLv3, kCFStreamSSLLevel,
nil];
CFReadStreamSetProperty((CFReadStreamRef)inputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
CFWriteStreamSetProperty((CFWriteStreamRef)outputStream, kCFStreamPropertySSLSettings, (CFTypeRef)settings);
[inputStream open];
[outputStream open];
}
- (BOOL) sendData:(NSString*)ns_SendData :(NSUInteger)nui_bufferLength
{
NSUInteger nui_length = [ns_SendData length];
NSUInteger nui_chunkSize = nui_bufferLength;
NSUInteger nui_offset = 0;
long l_Result;
NSString *ns_response = [NSString stringWithFormat:#"%#", ns_SendData];
NSData *nd_data = [[NSData alloc] initWithData:[ns_response dataUsingEncoding:NSASCIIStringEncoding]];
// a none-ASCII-symbol exists
if ([nd_data length] == 0)
return FALSE;
// split the data
do {
NSUInteger nui_thisChunkSize = nui_length - nui_offset > nui_chunkSize ? nui_chunkSize : nui_length - nui_offset;
NSData* chunk = [NSData dataWithBytesNoCopy:(char *)[nd_data bytes] + nui_offset
length:nui_thisChunkSize
freeWhenDone:NO];
nui_offset += nui_thisChunkSize;
// send data to server
l_Result = [outputStream write:[chunk bytes] maxLength:[chunk length]];
NSLog(#"%s - %lu", __PRETTY_FUNCTION__, l_Result);
if (l_Result == -1) {
NSLog(#"error sending data");
return false;
}
} while (nui_offset < nui_length);
return true;
}
The problem is solved by using TLS 1.2. See the AppTechnical Note TN2287
const void* keys[] = { kCFStreamSSLValidatesCertificateChain, kCFStreamSSLLevel };
// New CFString values available only on iOS 5:
// (if these values are used on versions before iOS 5, then will
// default to max TLS 1.0, min SSLv3)
// kCFStreamSocketSecurityLevelTLSv1_0SSLv3 configures max TLS 1.0, min SSLv3
// (same as default behavior on versions before iOS 5).
// kCFStreamSocketSecurityLevelTLSv1_0 configures to use only TLS 1.0.
// kCFStreamSocketSecurityLevelTLSv1_1 configures to use only TLS 1.1.
// kCFStreamSocketSecurityLevelTLSv1_2 configures to use only TLS 1.2.
const void* values[] = { CFSTR("kCFBooleanTrue"), CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") };
CFDictionaryRef sslSettingsDict = CFDictionaryCreate(kCFAllocatorDefault, keys, values, 2,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks);
CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, sslSettingsDict);
CFWriteStreamSetProperty(writeStream, kCFStreamPropertySSLSettings, sslSettingsDict);
CFRelease(sslSettingsDict);

Garbage reading from NSInputStream

Im using input streams to communicate with a POS device.
When i send the first request the response is normal. The problem comes when i send the second request because the second response is shorter than first one and that content is still in the stream. So i get the following:
First response:
<response>A really big response with much more things inside</response>
Second response;
<response>A not so big response</response>more things inside</response>
I do the following:
1) Open the streams
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, [port intValue], &readStream, &writeStream);
iStream = (NSInputStream *)readStream;
oStream = (NSOutputStream *)writeStream;
[iStream setDelegate:self];
[oStream setDelegate:self];
[iStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[oStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[iStream open];
[oStream open];
2) then i write my request:
[oStream write:(uint8_t *)[request UTF8String] maxLength:[request length]];
3) when the response arrives i capture it and send it to the parser:
uint8_t buffer[MAX_BUFFER_SIZE];
[iStream read:buffer maxLength:MAX_BUFFER_SIZE - 1];
NSString *response = [NSString stringWithUTF8String:(char *)buffer];
free(buffer);
NSError *error;
NSDictionary *ret =[SimpleXMLConverter dictionaryForXMLString:response error:&error];
After this, i tried to close the streams and open them again before sending the second request
Thanks and regards
PS: its not a blocking issue because i can get the correct substring, but i dont understand why its happening
That's because your buffer string is not \0(NULL) terminated.
Maybe uint8_t buffer[MAX_BUFFER_SIZE] = {0}; will solve your current problem.
However, you should consider one response could be longer than MAX_BUFFER_SIZE - 1.
Even if a response is shorter than it, read:maxLength: is not guaranteed to read all data at once. You must check the returned value and received data has entire response.
Moreover, DO NOT free(buffer), because free() is only for malloc()ed memory, not for auto variables.

iOS sockets: communication with Maya

Using this very helpful tutorial, I've been able to put together a test (video) of socket communication between the iOS Simulator and Maya. This is working very well with the simulator and localhost. However, when I test it on my device over the same wireless network, the iOS application just hangs with no connection with Maya (upon connection, there's a little "heads-up" message that displays).
I'm new to iOS programming and socket programming in particular, but I'm wondering if I'm missing anything. Here's the method I'm calling with the "connect" button press:
- (void) initNetworkCommunication {
//assign text inputs to variables
self.ipAddress = self.inputIPAddress.text;
self.portNumber = [self.inputPortNumber.text intValue];
//create streams and use variables to populate connection method
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)(self.ipAddress), self.portNumber, &readStream, &writeStream);
inputStream = (__bridge NSInputStream *)readStream;
outputStream = (__bridge NSOutputStream *)writeStream;
[inputStream setDelegate:self];
[outputStream setDelegate:self];
[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
[inputStream open];
[outputStream open];
//send initial message to Maya
NSString *initResponse = [NSString stringWithFormat:#"cmds.headsUpMessage('connected to iPhone')"];
NSData *initData = [[NSData alloc] initWithData:[initResponse dataUsingEncoding:NSASCIIStringEncoding]];
[outputStream write:[initData bytes] maxLength:[initData length]];
//need some error checking or timeout mechanism
}
Like I said, works like a charm with localhost, but fails over the wireless network.
Okay, found my own answer by digging through someone else's python script for creating a commandPort in Maya. It actually isn't due to the iOS code at all. It turns out that in Maya I need to create a commandPort using not only a port number but also the ip address. My mistake was that I assumed the commandPort knew the local ip address or didn't care. Apparently it cares.
Before (simplified code):
cmds.commandPort (n=':6328', stp='python')
After:
cmds.commandPort (n='192.168.2.7:6328', stp='python')

Resources