Async CFStream networking with runloop - iphone

I am trying to implement async tcp networking with runloop.
currently I manage to connect, but when I try to send something I get that -1 bytes have been written - but CFWriteStreamCopyError returns null.
code sample below, first function connects, second send a simple message.
any help will be appreciated, including random bug spotting (I am new to objective-c and to iphone development in general).
struct header
{
uint32_t length;
uint32_t type;
} header;
- (void) connect
{
NSLog(#"Attempting to (re)connect to %#:%d", m_host, m_port);
while(TRUE)
{
CFHostRef host = CFHostCreateWithName(kCFAllocatorDefault, (CFStringRef)m_host);
if (!host)
{
NSLog(#"Error resolving host %#", m_host);
[NSThread sleepForTimeInterval:5.0];
continue;
}
CFStreamCreatePairWithSocketToCFHost(kCFAllocatorDefault, host , m_port, &m_in, &m_out);
CFRelease(host);
if (!m_in)
{
NSLog(#"Error");
}
CFStreamClientContext context = {0, self,nil,nil,nil};
if (CFReadStreamSetClient(m_in, kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkReadEvent, &context))
{
CFReadStreamScheduleWithRunLoop(m_in, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
if (CFWriteStreamSetClient(m_out, kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered, networkWriteEvent, &context))
{
CFWriteStreamScheduleWithRunLoop(m_out, CFRunLoopGetCurrent(),kCFRunLoopCommonModes);
}
BOOL success = CFReadStreamOpen(m_in);
CFErrorRef error = CFReadStreamCopyError(m_in);
if (!success || (error && CFErrorGetCode(error) != 0))
{
NSLog(#"Connect error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
[NSThread sleepForTimeInterval:5.0];
}
else
{
NSLog(#"Connected");
break;
}
}
[self startSession];
}
- (void) startSession
{
struct header hh;
hh.type = RTR_CREATE_SESSION;
hh.length = 0;
CFIndex res = CFWriteStreamWrite(self.m_out, (const UInt8*)&hh, sizeof(hh));
NSLog(#"Written %d", res);
CFErrorRef error = CFWriteStreamCopyError(self.m_out);
if (error)
{
NSLog(#"Read error %s : %d", CFErrorGetDomain(error), CFErrorGetCode(error));
CFRelease(error);
}
}

figured it out, I forgot to open the write stream as well:
CFWriteStreamOpen(m_out);

Related

OSAtomicIncrement32Barrier deprecated, how to solve this issue?

i'm trying Parse server on my ios app using Xcode14 and ios 16, I installed the pod Parse, but when I run the code I get the following warning message:
'OSAtomicIncrement32Barrier' is deprecated: first deprecated in iOS 10.0 - Use atomic_fetch_add() from <stdatomic.h> instead
Any help how I could fix this issue:
+ (instancetype)taskForCompletionOfAllTasks:(nullable NSArray<BFTask *> *)tasks {
__block int32_t total = (int32_t)tasks.count;
if (total == 0) {
return [self taskWithResult:nil];
}
__block int32_t cancelled = 0;
NSObject *lock = [[NSObject alloc] init];
NSMutableArray *errors = [NSMutableArray array];
BFTaskCompletionSource *tcs = [BFTaskCompletionSource taskCompletionSource];
for (BFTask *task in tasks) {
[task continueWithBlock:^id(BFTask *t) {
if (t.error) {
#synchronized (lock) {
[errors addObject:t.error];
}
} else if (t.cancelled) {
OSAtomicIncrement32Barrier(&cancelled); // error is here
}
if (OSAtomicDecrement32Barrier(&total) == 0) { // error is here
if (errors.count > 0) {
if (errors.count == 1) {
tcs.error = [errors firstObject];
} else {
NSError *error = [NSError errorWithDomain:BFTaskErrorDomain
code:kBFMultipleErrorsError
userInfo:#{ BFTaskMultipleErrorsUserInfoKey: errors }];
tcs.error = error;
}
} else if (cancelled > 0) {
[tcs cancel];
} else {
tcs.result = nil;
}
}
return nil;
}];
}
return tcs.task;
}
C11 has support for atomics so you can do something like:
#include <stdatomic.h>
// Declare an atomic int and initialize it to zero
atomic_int total;
atomic_init(&total, 0);
// Note: C17 and C23 allow safe direct initialization
// i.e. atomic_int total = 0;
// Operations such as ++total and --total are atomic
++total;
// Or alternatively
atomic_fetch_add(&total, 1);
atomic_fetch_sub(&total, 1);

How do I setup CFReadStreamRead to timeout in iOS?

I have legacy code using CFReadStreamRead on the iOS, but if there is no/loss of a connection CFReadStreamRead will block forever. How do I setup CFReadStreamRead to timeout?
Thanks in advance
#Michael Wildermuth Yes, there was an error on opening the stream and got it fixed. Someone who face the same issue, the below code would help.
NSURL *url = [NSURL URLWithString:#"http://www.google.com"];
CFStreamClientContext dataStreamContext = {0, (__bridge void *)(self), NULL, NULL, NULL};
CFHTTPMessageRef message = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_1);
NSString *header;
NSDictionary *requestHeaders = [NSDictionary dictionaryWithObject:#"application/html;charset=UTF-8" forKey:#"Content-Type"];
for (header in requestHeaders) {
CFHTTPMessageSetHeaderFieldValue(message, (__bridge CFStringRef)header, (__bridge CFStringRef)[requestHeaders objectForKey:header]);
}
CFHTTPMessageSetBody(message, (CFDataRef)(CFSTR("")));
CFReadStreamRef readStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, message);
CFOptionFlags events = kCFStreamEventHasBytesAvailable | kCFStreamEventErrorOccurred | kCFStreamEventEndEncountered;
if(CFReadStreamSetClient(readStream, events, EvenCallBack, &dataStreamContext)){
CFReadStreamScheduleWithRunLoop(readStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes);
}
CFReadStreamOpen(readStream);
And for the callback function,
void EvenCallBack(CFReadStreamRef readStream, CFStreamEventType type, void *clientCallBackInfo){
if(CFReadStreamHasBytesAvailable(readStream))
{
uint8_t buf[1024];
unsigned int len = 1024;
CFIndex numBytesRead = CFReadStreamRead(readStream, buf, len);
NSMutableData* data = [[NSMutableData alloc] init];
[data appendBytes:&buf length:numBytesRead];
NSString *str = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"Network read (%d): %#", len, str);
}else {
return;
}
CFHTTPMessageRef message = (CFHTTPMessageRef)CFReadStreamCopyProperty((CFReadStreamRef)readStream, kCFStreamPropertyHTTPResponseHeader);
if (!message) {
NSLog(#"No message");
}
}
Thanks!
I ended up using a home grown timeout. The code was something like this:
uint64_t start = get_current_time();
while(TRUE) {
if(CFReadStreamHasBytesAvailable(stream) == TRUE) {
while ((bytes_read = CFReadStreamRead(stream, buffer, read_size)) > 0) {
// do work!
}
start = get_current_time();
}
uint64_t elapsed = get_current_time() - start;
if(elapsed > timeout) {
break;
}
sleep(10);
}
while ((bytes_read = CFReadStreamRead(stream, buffer, read_size)) > 0) {
// do work!
}
I don't think this code achieves anything special. CFReadStreamRead is internally implemented in a similar manner. Quoting Apple's documentation on this: "This function blocks until at least one byte is available; it does not block until buffer is filled."
I set a custom timeout method:
var hasRecievedUpdate = false
var httpStream: CFReadStream?
var handler: ((Data?, URLResponse?, Error?) -> Void)?
func send() {
let stream = httpStream as Stream
stream.delegate = self
stream.schedule(in: RunLoop.current, forMode: RunLoop.Mode.default)
stream.open()
hasRecievedUpdate = false
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + self.timeout) {
if !self.hasRecievedUpdate
{
stream.close()
self.handler?(nil, nil, nil)
}
}
}
func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
self.hasRecievedUpdate = true
...
}

CFStream IOS Socket communication

how can i clear an CFStream buffer?
Everytime i read from socket there is still data from an old request, i mean complete response to an old request not just a fragment of it.
Am i missing something ?
This is a function i use to initialize the connection:
-(void)openSocketConnection:(UInt32)port: (NSString *)host
{
NSString *hoststring = [[NSString alloc] initWithString:host];
CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault,(__bridge CFStringRef)hoststring ,
port,&_nnet_readStream,&_nnet_writeStream);
CFWriteStreamCanAcceptBytes(_nnet_writeStream);
CFWriteStreamSetProperty(_nnet_writeStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
CFReadStreamSetProperty(_nnet_readStream, kCFStreamPropertyShouldCloseNativeSocket, kCFBooleanTrue);
if(!CFWriteStreamOpen(_nnet_writeStream)) {
NSLog(#"Error Opening Write Socket");
}
if(!CFReadStreamOpen(_nnet_readStream)) {
NSLog(#"Error Opening Write Socket");
}
}
This is a function i use to read data from socket:
BOOL done = NO;
NSMutableString* result = [NSMutableString string];
while (!done)
{
if (CFReadStreamHasBytesAvailable(_nnet_readStream))
{
UInt8 buf[1024];
CFIndex bytesRead = CFReadStreamRead(_nnet_readStream, buf, 1024);
if (bytesRead < 0)
{
CFStreamError error = CFReadStreamGetError(_nnet_readStream);
NSLog(#"%#",error);
} else if (bytesRead == 0)
{
if (CFReadStreamGetStatus(_nnet_readStream) == kCFStreamStatusAtEnd)
{
done = YES;
}
} else
{
[result appendString:[[NSString alloc] initWithBytes:buf length:bytesRead encoding:NSUTF8StringEncoding]];
}
} else
{
done = YES;
}
}
You seem to assume that when you get data from the host on the other end it will always be available all in one go.
I don't know what the underlying protocol is, but in general it's entirely possible that the remote end will try and send 1600 bytes and that you'll get 1500 bytes but then have to wait a few milliseconds (or even seconds) for the next 100 bytes. Meanwhile CFReadStreamHasBytesAvailable would return false and so your code would set your done flag even though you haven't read all the data the server sent.

NSPOSIXErrorDomain Code=12 "Cannot allocate memory" in 3G network

I'm trying to send a file on FTP server in my iPhone application.
Everything seems to be okay in WiFi and GSM:EDGE network, but in 3G network an error appears (not always, but very often):
Error Domain=NSPOSIXErrorDomain
Code=12 "The operation couldn’t be
completed. Cannot allocate memory"
Below the code where the error appears:
- (void)stream:(NSStream*)aStream handleEvent:(NSStreamEvent)eventCode {
switch( eventCode ) {
case NSStreamEventHasSpaceAvailable: {
if( _readDataOffset == _readDataLimit ) {
NSInteger readDataLen = [_readStream read:[_readData mutableBytes] maxLength:kReadDataLength];
NSLog(#"readDataLen is %d",readDataLen);
if( -1 == readDataLen ) {
_error = [[_readStream streamError] retain];
_keepRunning = NO;
} else if( 0 == readDataLen ) {
_keepRunning = NO;
} else {
_readDataOffset = 0;
_readDataLimit = readDataLen;
}
}
if( _readDataOffset != _readDataLimit ) {
NSOutputStream* writeStream = (NSOutputStream*)aStream;
uint8_t *buffer = (void *)[_readData bytes];
// vvvv and here the value of writtenDataLen is often -1 (but only on 3G network)
NSInteger writtenDataLen = [writeStream write:&buffer[_readDataOffset] maxLength:_readDataLimit - _readDataOffset];
if( writtenDataLen > 0 ) {
_readDataOffset += writtenDataLen;
_writtenDataLen += writtenDataLen;
[self ftpPutDidWriteInternal];
} else if( -1 == writtenDataLen ) {
_error = [[writeStream streamError] retain];
_keepRunning = NO;
}
}
} break;
case NSStreamEventErrorOccurred: {
_error = [aStream.streamError retain];
_keepRunning = NO;
} break;
}
}
What can be important, the whole sending is done in separate thread which has it's own NSAutoreleasePool.
Is there anyone who got the issue? Any suggestion? I would be appreciate.
UPDATE:
I've just checked that popular iPhone application "FTP On The Go" has got the same (?) issue during sending a file in 3G network! There is no error handled, but the transfer stops.
UPDATE 2:
I can't believe it, but it's true: SimpleFTPSample from Apple is also affected with the issue.
And here it is - the solution (or rather workaround):
you should set property of writeStream to false, to switch off default persistant connection
CFWriteStreamSetProperty( (CFWriteStreamRef)writeStreamRef, kCFStreamPropertyFTPAttemptPersistentConnection, kCFBooleanFalse ) ;
Have resolved this error with using operation for request (NSMutableUrlConnection) with #autorelease{} for main function
- (void)main
NSURLConnection* connection;
#autoreleasepool //urgently needed for 3G upload
{
self.currentRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"test.php"]];
[self.currentRequest setHTTPMethod:#"PUT"];
[self.currentRequest setHTTPBody:self.data];//inpustStream doesn't work
connection = [NSURLConnection connectionWithRequest:self.currentRequest delegate:self];
[connection start];
}//end autorelease pool
do
{
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];
if ([self isCancelled])
{
connection = nil;
isFailed = YES;
break;
}
self.status(statusUpdateMessage);
}
while (!isFailed && !isCompleted);
[timer invalidate];//test
timer = nil;
//corresponding of status via blocks
self.completed(!isFailed);
self.status(isFailed ? errorMessage : #"Completed");
if (isFailed)
{
self.failed(errorMessage != nil ? errorMessage : #"Undefined error");
}
self.data = nil;
self.currentRequest = nil;
connection = nil;
}

Trying to find USB device on iphone with IOKit.framework

I'm working on a project were I need the USB port to communicate with an external device. I have been looking for examples on the net (Apple and /developer/IOKit/usb exemple) and trying some others, but I can't even find the device.
In my code, I'm blocking at the place where the function looks for a next iterator (pointer in fact) with the function getNextIterator; but it never returns a good value, so the code is blocking. By the way, I am using toolchain and added IOKit.framework in my project. All I want right now is to communicate or do like a ping to someone on the USB bus! I'm blocking in FindDevice... I can't manage to enter in the while loop because the variable usbDevice is always = to 0... I have tested my code in a small mac program and it works...
Here is my code :
IOReturn ConfigureDevice(IOUSBDeviceInterface **dev) {
UInt8 numConfig;
IOReturn result;
IOUSBConfigurationDescriptorPtr configDesc;
//Get the number of configurations
result = (*dev)->GetNumberOfConfigurations(dev, &numConfig);
if (!numConfig) {
return -1;
}
// Get the configuration descriptor
result = (*dev)->GetConfigurationDescriptorPtr(dev, 0, &configDesc);
if (result) {
NSLog(#"Couldn't get configuration descriptior for index %d (err=%08x)\n", 0, result);
return -1;
}
#ifdef OSX_DEBUG
NSLog(#"Number of Configurations: %d\n", numConfig);
#endif
// Configure the device
result = (*dev)->SetConfiguration(dev, configDesc->bConfigurationValue);
if (result)
{
NSLog(#"Unable to set configuration to value %d (err=%08x)\n", 0, result);
return -1;
}
return kIOReturnSuccess;
}
IOReturn FindInterfaces(IOUSBDeviceInterface **dev, IOUSBInterfaceInterface ***itf) {
IOReturn kr;
IOUSBFindInterfaceRequest request;
io_iterator_t iterator;
io_service_t usbInterface;
IOUSBInterfaceInterface **intf = NULL;
IOCFPlugInInterface **plugInInterface = NULL;
HRESULT res;
SInt32 score;
UInt8 intfClass;
UInt8 intfSubClass;
UInt8 intfNumEndpoints;
int pipeRef;
CFRunLoopSourceRef runLoopSource;
NSLog(#"Debut FindInterfaces \n");
request.bInterfaceClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceSubClass = kIOUSBFindInterfaceDontCare;
request.bInterfaceProtocol = kIOUSBFindInterfaceDontCare;
request.bAlternateSetting = kIOUSBFindInterfaceDontCare;
kr = (*dev)->CreateInterfaceIterator(dev, &request, &iterator);
usbInterface = IOIteratorNext(iterator);
IOObjectRelease(iterator);
NSLog(#"Interface found.\n");
kr = IOCreatePlugInInterfaceForService(usbInterface, kIOUSBInterfaceUserClientTypeID, kIOCFPlugInInterfaceID, &plugInInterface, &score);
kr = IOObjectRelease(usbInterface); // done with the usbInterface object now that I have the plugin
if ((kIOReturnSuccess != kr) || !plugInInterface)
{
NSLog(#"unable to create a plugin (%08x)\n", kr);
return -1;
}
// I have the interface plugin. I need the interface interface
res = (*plugInInterface)->QueryInterface(plugInInterface, CFUUIDGetUUIDBytes(kIOUSBInterfaceInterfaceID), (LPVOID*) &intf);
(*plugInInterface)->Release(plugInInterface); // done with this
if (res || !intf)
{
NSLog(#"couldn't create an IOUSBInterfaceInterface (%08x)\n", (int) res);
return -1;
}
// Now open the interface. This will cause the pipes to be instantiated that are
// associated with the endpoints defined in the interface descriptor.
kr = (*intf)->USBInterfaceOpen(intf);
if (kIOReturnSuccess != kr)
{
NSLog(#"unable to open interface (%08x)\n", kr);
(void) (*intf)->Release(intf);
return -1;
}
kr = (*intf)->CreateInterfaceAsyncEventSource(intf, &runLoopSource);
if (kIOReturnSuccess != kr)
{
NSLog(#"unable to create async event source (%08x)\n", kr);
(void) (*intf)->USBInterfaceClose(intf);
(void) (*intf)->Release(intf);
return -1;
}
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, kCFRunLoopDefaultMode);
if (!intf)
{
NSLog(#"Interface is NULL!\n");
} else
{
*itf = intf;
}
NSLog(#"End of FindInterface \n \n");
return kr;
}
unsigned int FindDevice(void *refCon, io_iterator_t iterator) {
kern_return_t kr;
io_service_t usbDevice;
IOCFPlugInInterface **plugInInterface = NULL;
HRESULT result;
SInt32 score;
UInt16 vendor;
UInt16 product;
UInt16 release;
unsigned int count = 0;
NSLog(#"Searching Device....\n");
while (usbDevice = IOIteratorNext(iterator))
{
// create intermediate plug-in
NSLog(#"Found a device!\n");
kr = IOCreatePlugInInterfaceForService(usbDevice,
kIOUSBDeviceUserClientTypeID,
kIOCFPlugInInterfaceID,
&plugInInterface, &score);
kr = IOObjectRelease(usbDevice);
if ((kIOReturnSuccess != kr) || !plugInInterface) {
NSLog(#"Unable to create a plug-in (%08x)\n", kr);
continue;
}
// Now create the device interface
result = (*plugInInterface)->QueryInterface(plugInInterface,
CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID),
(LPVOID)&dev);
// Don't need intermediate Plug-In Interface
(*plugInInterface)->Release(plugInInterface);
if (result || !dev) {
NSLog(#"Couldn't create a device interface (%08x)\n",
(int)result);
continue;
}
// check these values for confirmation
kr = (*dev)->GetDeviceVendor(dev, &vendor);
kr = (*dev)->GetDeviceProduct(dev, &product);
//kr = (*dev)->GetDeviceReleaseNumber(dev, &release);
//if ((vendor != LegoUSBVendorID) || (product != LegoUSBProductID) || (release != LegoUSBRelease)) {
if ((vendor != LegoUSBVendorID) || (product != LegoUSBProductID))
{
NSLog(#"Found unwanted device (vendor = %d != %d, product = %d != %d, release = %d)\n",
vendor, kUSBVendorID, product, LegoUSBProductID, release);
(void) (*dev)->Release(dev);
continue;
}
// Open the device to change its state
kr = (*dev)->USBDeviceOpen(dev);
if (kr == kIOReturnSuccess) {
count++;
} else {
NSLog(#"Unable to open device: %08x\n", kr);
(void) (*dev)->Release(dev);
continue;
}
// Configure device
kr = ConfigureDevice(dev);
if (kr != kIOReturnSuccess) {
NSLog(#"Unable to configure device: %08x\n", kr);
(void) (*dev)->USBDeviceClose(dev);
(void) (*dev)->Release(dev);
continue;
}
break;
}
return count;
}
// USB rcx Init
IOUSBInterfaceInterface** osx_usb_rcx_init (void)
{
CFMutableDictionaryRef matchingDict;
kern_return_t result;
IOUSBInterfaceInterface **intf = NULL;
unsigned int device_count = 0;
// Create master handler
result = IOMasterPort(MACH_PORT_NULL, &gMasterPort);
if (result || !gMasterPort)
{
NSLog(#"ERR: Couldn't create master I/O Kit port(%08x)\n", result);
return NULL;
}
else {
NSLog(#"Created Master Port.\n");
NSLog(#"Master port 0x:08X \n \n", gMasterPort);
}
// Set up the matching dictionary for class IOUSBDevice and its subclasses
matchingDict = IOServiceMatching(kIOUSBDeviceClassName);
if (!matchingDict) {
NSLog(#"Couldn't create a USB matching dictionary \n");
mach_port_deallocate(mach_task_self(), gMasterPort);
return NULL;
}
else {
NSLog(#"USB matching dictionary : %08X \n", matchingDict);
}
CFDictionarySetValue(matchingDict, CFSTR(kUSBVendorID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &LegoUSBVendorID));
CFDictionarySetValue(matchingDict, CFSTR(kUSBProductID),
CFNumberCreate(kCFAllocatorDefault, kCFNumberShortType, &LegoUSBProductID));
result = IOServiceGetMatchingServices(gMasterPort, matchingDict, &gRawAddedIter);
matchingDict = 0; // this was consumed by the above call
// Iterate over matching devices to access already present devices
NSLog(#"RawAddedIter : 0x:%08X \n", &gRawAddedIter);
device_count = FindDevice(NULL, gRawAddedIter);
if (device_count == 1)
{
result = FindInterfaces(dev, &intf);
if (kIOReturnSuccess != result)
{
NSLog(#"unable to find interfaces on device: %08x\n", result);
(*dev)->USBDeviceClose(dev);
(*dev)->Release(dev);
return NULL;
}
// osx_usb_rcx_wakeup(intf);
return intf;
}
else if (device_count > 1)
{
NSLog(#"too many matching devices (%d) !\n", device_count);
}
else
{
NSLog(#"no matching devices found\n");
}
return NULL;
}
int main(int argc, char *argv[])
{
int returnCode;
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSLog(#"Debut du programme \n \n");
osx_usb_rcx_init();
NSLog(#"Fin du programme \n \n");
return 0;
// returnCode = UIApplicationMain(argc, argv, #"Untitled1App", #"Untitled1App");
// [pool release];
// return returnCode;
}
IOKit is not available for iPhone applications. If you need to connect with external devices from the iPhone you need to sign up for the MFi Program which will provide you with the needed API's and documentation.
besides the appstore rules i dont think u can even touch iokit on iOS without violating the sdk's agreement.