How to use CFNetwork to get byte array from sockets? - objective-c

I'm working in a project for the iPad, it is a small program and I need it to communicate with another software that runs on windows and act like a server; so the application that I'm creating for the iPad will be the client.
I'm using CFNetwork to do sockets communication, this is the way I'm establishing the connection:
char ip[] = "192.168.0.244";
NSString *ipAddress = [[NSString alloc] initWithCString:ip];
/* Build our socket context; this ties an instance of self to the socket */
CFSocketContext CTX = { 0, self, NULL, NULL, NULL };
/* Create the server socket as a TCP IPv4 socket and set a callback */
/* for calls to the socket's lower-level connect() function */
TCPClient = CFSocketCreate(NULL, PF_INET, SOCK_STREAM, IPPROTO_TCP, kCFSocketDataCallBack, (CFSocketCallBack)ConnectCallBack, &CTX);
if (TCPClient == NULL)
return;
/* Set the port and address we want to listen on */
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr([ipAddress UTF8String]);
CFDataRef connectAddr = CFDataCreate(NULL, (unsigned char *)&addr, sizeof(addr));
CFSocketConnectToAddress(TCPClient, connectAddr, -1);
CFRunLoopSourceRef sourceRef = CFSocketCreateRunLoopSource(kCFAllocatorDefault, TCPClient, 0);
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopCommonModes);
CFRelease(sourceRef);
CFRunLoopRun();
And this is the way I sent the data, which basically is a byte array
/* The native socket, used for various operations */
// TCPClient is a CFSocketRef member variable
CFSocketNativeHandle sock = CFSocketGetNative(TCPClient);
Byte byteData[3];
byteData[0] = 0;
byteData[1] = 4;
byteData[2] = 0;
send(sock, byteData, strlen(byteData)+1, 0);
Finally, as you may have noticed, when I create the server socket, I registered a callback for the kCFSocketDataCallBack type, this is the code.
void ConnectCallBack(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info)
{
// SocketsViewController is the class that contains all the methods
SocketsViewController *obj = (SocketsViewController*)info;
UInt8 *unsignedData = (UInt8 *) CFDataGetBytePtr(data);
char *value = (char*)unsignedData;
NSString *text = [[NSString alloc]initWithCString:value length:strlen(value)];
[obj writeToTextView:text];
[text release];
}
Actually, this callback is being invoked in my code, the problem is that I don't know how can I get the data that the windows client sent me, I'm expecting to receive an array of bytes, but I don't know how can I get those bytes from the data param.
If anyone can help me to find a way to do this, or maybe me point me to another way to get the data from the server in my client application I would really appreciate it.
Thanks.

In your callback, the data parameter is indeed a CFDataRef value for the kCFSocketDataCallBack callback type.
CFDataRef dataRef = (CFDataRef) data;
Byte *array = new Byte[CFDataGetLength(dataRef)]; // Or use a fixed length
CFDataGetBytes(dataRef, CFRangeMake(0, CFDataGetLength(theData)), array);
The array will then contains the array of byte.

Related

Calling CFRelease for a CFHostRef will sometimes crash

This is a secondary question that arose out of a post I made earlier today. I have the method below, which works fine for what I need, but sometimes crashes when I call CFRelease on the hostRef variable. I think it may have to do with the resource being used elsewhere when I'm trying to release it, but as far as I can tell, I'm synchronously resolving the host and I'm not accessing it from another thread.
I tried calling CFHostCancelInfoResolution before CFRelease, but that didn't change the frequency of the crashes. I thought I would post this here to see if there are some assumptions or misconceptions I have that aren't true.
+ (NSArray *) addressesForHostname: (NSString *)hostname {
CFMutableArrayRef ipAddresses = nil;
DLog(#"Getting addresses for host name %#", hostname);
CFHostRef hostRef = CFHostCreateWithName(kCFAllocatorDefault, (__bridge CFStringRef)(hostname));
CFStreamError error;
BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostAddresses, &error); // synchronously get the host.
if (didResolve) {
CFArrayRef responseObjects = CFHostGetAddressing(hostRef, NULL);
long numberOfResponses = CFArrayGetCount(responseObjects);
ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);
for ( int i = 0 ; i < numberOfResponses; ++i ) {
char * ipAddress = NULL;
CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
struct sockaddr * currentAddress = (struct sockaddr *) CFDataGetBytePtr(responseObject); // Unwrap the CFData wrapper aound the sockaddr struct
switch (currentAddress->sa_family) {
case AF_INET: { // Internetworking AKA IPV4
DLog(#"Extracting IPV4 address");
struct sockaddr_in * socketAddress = (struct sockaddr_in *) currentAddress;
ipAddress = malloc(sizeof(INET_ADDRSTRLEN));
inet_ntop(AF_INET,
&(socketAddress->sin_addr),
ipAddress,
INET_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
break;
}
case AF_INET6: { // IPV6
DLog(#"Extracting IPV6 address");
struct sockaddr_in6 * socketAddress = (struct sockaddr_in6 *) currentAddress;
ipAddress = malloc(sizeof(INET6_ADDRSTRLEN));
inet_ntop(AF_INET6,
&(socketAddress->sin6_addr),
ipAddress,
INET6_ADDRSTRLEN);
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
CFArrayInsertValueAtIndex(ipAddresses, i, ipAddressString);
break;
}
default:
DLog(#"Unsupported addressing protocol encountered. Gracefully ignoring and continuing.");
break;
}
if(ipAddress != NULL) {
free(ipAddress);
}
}
CFRelease(responseObjects);
}
CFRelease(hostRef);
return (__bridge_transfer NSArray *) ipAddresses;
}
removing CFRelease(responseObjects); shall fix it
You must follow the Create Rule for Core Foundation objects. If you received the object by calling a function with the words Create or Copy in their names (or if you call CFRetain explicitly), then you must release (CFRelease) the object when you're done with it. If you did not receive the object this way, then you must not release the object.
There are several mistakes in your code. First, the one you're finding, which is around responseObjects. You fetch this object using:
CFDataRef responseObject = CFArrayGetValueAtIndex(responseObjects, i);
The function does not have Create or Copy in its name. You must not call CFRelease on it.
However, you also call this:
ipAddresses = CFArrayCreateMutable(kCFAllocatorDefault, numberOfResponses, &kCFTypeArrayCallBacks);
and in a couple of places:
CFStringRef ipAddressString = CFStringCreateWithCString(kCFAllocatorDefault, ipAddress, kCFStringEncodingASCII);
You must call CFRelease on these objects before they go out of scope or you will leak them.
I think CFStreamError error; may cause this issue.
Try to declare error to null?
// synchronously get the host.
BOOL didResolve = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL);

Sending UDP packets in Objective C: packet arriving without any data

I'm trying to send UDP packets in objective C. More specifically, building with xcode targeting the iphone 6.1 simulator.
I can't seem to actually receive the data I send. Weirdly, I do get a data event... the data's just been truncated to length 0.
I've cut it down as much as I can, to make a dead simple test I think should pass.
#import "UdpSocketTest.h"
#include <arpa/inet.h>
#import <CoreFoundation/CFSocket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#implementation UdpSocketTest
static int receivedByteCount = 0;
void onReceive(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info);
void onReceive(CFSocketRef socket, CFSocketCallBackType type, CFDataRef address, const void *data, void *info) {
// this gets called once, but CFDataGetLength(data) == 0
receivedByteCount += CFDataGetLength(data);
}
-(void) testUdpSocket {
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(5000); // <-- doesn't really matter, not sending from receiver
inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr);
CFSocketContext socketContext = { 0, (__bridge void*)self, CFRetain, CFRelease, NULL };
// prepare receiver
CFSocketRef receiver = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP ,kCFSocketDataCallBack, (CFSocketCallBack)onReceive, &socketContext);
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFSocketCreateRunLoopSource(NULL, receiver, 0), kCFRunLoopCommonModes);
CFSocketConnectToAddress(receiver, CFDataCreate(NULL, (unsigned char *)&addr, sizeof(addr)), -1);
// point sender at receiver
CFSocketRef sender = CFSocketCreate(kCFAllocatorDefault, PF_INET, SOCK_DGRAM, IPPROTO_UDP, kCFSocketDataCallBack, (CFSocketCallBack)onReceive, &socketContext);
CFRunLoopAddSource(CFRunLoopGetCurrent(), CFSocketCreateRunLoopSource(NULL, sender, 0), kCFRunLoopCommonModes);
CFSocketConnectToAddress(sender, CFSocketCopyAddress(receiver), -1);
// send data of sixty zeroes, allow processing to occur
CFSocketSendData(sender, NULL, (__bridge CFDataRef)[NSMutableData dataWithLength:60], 2.0);
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
// did the data arrive?
STAssertTrue(receivedByteCount > 0, #"");
// nope
}
#end
What am I doing wrong? I know it's something obvious, but I can't see it.
I'm surprised you're getting any callbacks at all—you're never actually binding your receiver to your local port. You're creating two sockets and telling both of them "I went to send data to 127.0.0.1:5000", but nowhere are you saying "I want to receive data on 127.0.0.1:5000.
In order to do that, you should be calling CFSocketSetAddress on the receiver socket, not CFSocketConnectToAddress. This is equivalent to calling the bind(2) system call on the underlying native BSD socket.

What is wrong with my sockets code?

I'm working on an iOS version of my Android application which constantly sends some binary data to server over regular TCP connection. And while Java code works fine, my code in plain C doesn't - the connection breaks after the first bunch of data is sent to server. I managed to reimplement Java solution using CFNetwork framework, however my C code is still a problem...
C code (error checking and uninteresting code is removed):
mSocket = socket(AF_INET, SOCK_STREAM, 0);
//...
connect(mSocket, (struct sockaddr *)&sin, sizeof(sin));
int keepalive = 1;
int res = setsockopt(mSocket, SOL_SOCKET, SO_KEEPALIVE, (void *)&keepalive,
sizeof(int));
int set = 1;
setsockopt(mSocket, SOL_SOCKET, SO_NOSIGPIPE, (void *)&set, sizeof(int));
unsigned char buffer* = //...;
int ret = send(mSocket, buffer, bufferLen, 0); //handshake with server, runs fine
while(/*there is new data*/) {
buffer = new_data(); //some fake method
ret = send(mSocket, buffer, bufferLen, 0); //On the second iteration
//write returns -1
}
However, this Objective-C code based on CFNetwork works just fine...
Objective-C code:
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,
host,
port,
&readStream,
&writeStream);
CFWriteStreamOpen(mServerWriteStream);
while(/*there is new data*/) {
NSData* data = //....
int wroteBytes = CFWriteStreamWrite(mServerWriteStream, [data bytes], [data length]);
}
So I'm puzzled with what am I doing wrong in C code and will appreciate any hints
Thank you
If you need a regular TCP connection you need to create the socket with such protocol. Instead of:
mSocket = socket(AF_INET, SOCK_STREAM, 0);
You should create it as:
#include <netinet/in.h>
mSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
On my system IPPROTO_TCP is defined as 6 and not 0. I suspect you have something similar.
The setsockopt() has to execute before connect() or it is ignored.

CFSocketSetAddress fails if not host computer IP?

I am making a simple WOL application.
So far I can successfully create a socket, however when setting the address using CFSocketSetAddress I can only use the network ip of my computer (WiFi ip = 192.168.0.5) or local ip (127.0.0.1).
For WOL I would like to send the data to the broadcast address (255.255.255.255). If this is entered I am returned with the error 'address could not be set'.
Am I miss-understanding the use of CFSocketSetAddress, and the address is supposed to be the hosts IP, or the destination IP? In either case, what do I need to do, so that my destination ip is the broadcast address?
Below is some of my code:
/*************************************************************************/
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); //port
inet_aton(IP, &addr.sin_addr);//IP is the host network ip: 192.168.0.5
NSData *address = [NSData dataWithBytes: &addr length: sizeof(addr)];
if (CFSocketSetAddress(WOLsocket, (CFDataRef)address) != kCFSocketSuccess){
NSLog(#"Address could not be set!");
}
I solved my problem by using a different method (Got native socket), and then instead of using CFSocketSetAddress, I passed the address in the second argument of CFSocketSendData.
I don't have a link/reference for the changes, as it was some code stored on my HDD from days of heavy googling.
Would like to say thanks to David Gelhar Who pointed me in the right direction from my previous question.
for anyone else who might need this this is my code;
//Gets native & sets options
/*************************************************************************/
int desc = -1;
desc = CFSocketGetNative(WOLsocket);
int yes = 1;
if (setsockopt (desc, SOL_SOCKET, SO_BROADCAST, (char *)&yes, sizeof (yes)) < 0) {
NSLog(#"Set Socket options failed");
return EXIT_FAILURE;
}
//sets address socket - doesn't bind this is done in CFSocketSendData
/*************************************************************************/
unsigned long bcast = 0xffffffff;
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_len = sizeof(addr);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT); //port
addr.sin_addr.s_addr = bcast;
NSData *address = [NSData dataWithBytes: &addr length: sizeof(addr)];
//Sends Data
/*************************************************************************/
char ethadd []= "helloworld";
CFDataRef Data = CFDataCreate(NULL, (const UInt8*)ethadd, sizeof(ethadd));
if (CFSocketSendData(WOLsocket, address, Data, 0) < 0){
NSLog(#"Data could not be sent!");
return EXIT_FAILURE;
}
else NSLog(#"Data Sent");
}
For my app, the problem is that it was sandboxed, but it wasn't given network permissions.
If you're using App Sandbox, you need to check one of these:

iOS - How do I create a read and write stream to a network socket?

I am new to iOS development. I am attempting to create a read and write stream. I am using the CFNetworking programming guide's examples to try and get something working.
I am trying to schedule the read stream on the run loop to work around the issue of the streams blocking. Right away I have run into issues. How can I create a CFHost object using CFHhostCreateWithAddress? Here is what I have so far:
NSString *address = #"irc.ubuntu.net";
CFDataRef addressDataRef = (CFDataRef)[address dataUsingEncoding:NSASCIIStringEncoding];
CFHostRef host = CFHostCreateWithAddress(kCFAllocatorDefault, addressDataRef);
//Create Read and Write Stream
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host, 8008, &readStream, &writeStream);
The second line bombs. Can someone please tell me how to create a CFHostRef?
Thanks a lot!
The documentation states that the second argument to CFHostCreateWithAddress() must be "A CFDataRef object containing a sockaddr structure for the address of the host. This value must not be NULL."
You're passing a CFDataRef representing "irc.ubuntu.net", which is by no means a sockaddr struct.
Use CFHostCreateWithName:
CFHostRef CFHostCreateWithName (
CFAllocatorRef allocator,
CFStringRef hostname
);
As you probably know, you can cast an NSString * to CFStringRef, or create a constant CFStringRef with the macro CFSTR().
+ (NSData *)dataForIPAddress:(NSString *)address {
struct addrinfo hints;
struct addrinfo *result = NULL;
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_NUMERICHOST;
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;
int errorStatus = getaddrinfo([address cStringUsingEncoding:NSASCIIStringEncoding], NULL, &hints, &result);
if (errorStatus != 0) return nil;
CFDataRef addressRef = CFDataCreate(NULL, (UInt8 *)result->ai_addr, result->ai_addrlen);
if (addressRef == nil) return nil;
freeaddrinfo(result);
return [(NSData *)addressRef autorelease];
}