I'm trying to connect my users via SSL from my iOS XMPP chat client to Openfire server.
In my iOS client:
- (void)setupStream
{
...
// BOOL values for security settings
customCertEvaluation = NO;
allowSelfSignedCertificates = YES;
allowSSLHostNameMismatch = NO;
}
In my Openfire server's Security Settings > Client Connection Security, I've set:
Required - Clients can only connect to the server using secured connections.
Thus, the following delegate method will be called:
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
{
NSString *expectedCertName = [xmppStream.myJID domain];
if (customCertEvaluation)
[settings setObject:#(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];
if (allowSelfSignedCertificates)
[settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];
if (allowSSLHostNameMismatch)
[settings setObject:[NSNull null] forKey:(NSString *)kCFStreamSSLPeerName];
else
if (expectedCertName)
[settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName];
}
I attempted this solution from this thread: XMPPFramework TLS/SSL connection with Openfire
However, when I run my application and attempt to connect to the server, I'd receive this error:
Security option unavailable - kCFStreamSSLAllowsAnyRoot - You must use manual trust evaluation
I looked through the GCDAsyncSocket class and realized kCFStreamSSLAllowsAnyRoot is stated as deprecated. An NSAssert was implemented to deliberately throw the error.
Next, I decided to change my BOOL values as such:
- (void)setupStream
{
...
// BOOL values for security settings
// Manually evaluate trust
customCertEvaluation = YES;
allowSelfSignedCertificates = NO;
allowSSLHostNameMismatch = NO;
}
This time, again, no connection could be made to the server but, no error was prompted.
I could connect to Openfire fine if I changed the Client Connection Security back to the original setting > Optional. But, I wouldn't be connected via SSL as indicated by a lock icon beside every user's status in Client Sessions.
My Android client (using Smack API for XMPP) connects to Openfire via SSL without issues. So I'm wondering if there's workaround I have to implement for my iOS client using XMPPFramework.
I would greatly appreciate any advices.
Explanation
In the latest version of XMPP (after April 22), you can no longer use allowSelfSignedCertificates = YES with the following:
if (allowSelfSignedCertificates)
[settings setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLAllowsAnyRoot];`
This is because kCFStreamSSLAllowsAnyRoot & SSLSetAllowsAnyRoot have been deprecated.
/*
* ==== The following UNAVAILABLE KEYS are: (with throw an exception)
* - kCFStreamSSLAllowsAnyRoot (UNAVAILABLE)
* You MUST use manual trust evaluation instead (see GCDAsyncSocketManuallyEvaluateTrust).
* Corresponding deprecated method: SSLSetAllowsAnyRoot
*/
See XMPPFramework/GCDAsyncSocket.h & Deprecated Secure Transport Functions.
Solution
Go to Openfire server > Security Settings > Client Connection Security
Check: Required - Clients can only connect to the server using secured connections.
Define variable in AppDelegate
BOOL customCertEvaluation;
Set variable in setupStream
- (void)setupStream
{
...
customCertEvaluation = YES;
}
Set security settings in willSecureWithSettings
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
{
/*
* Properly secure your connection by setting kCFStreamSSLPeerName
* to your server domain name
*/
[settings setObject:xmppStream.myJID.domain forKey:(NSString *)kCFStreamSSLPeerName];
/*
* Use manual trust evaluation
* as stated in the XMPPFramework/GCDAsyncSocket code documentation
*/
if (customCertEvaluation)
[settings setObject:#(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];
}
Validate peer manually
/*
* This is only called if the stream is secured with settings that include:
* - GCDAsyncSocketManuallyEvaluateTrust == YES
* That is, if a delegate implements xmppStream:willSecureWithSettings:, and plugs in that key/value pair.
*/
- (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
{
/* Custom validation for your certificate on server should be performed */
completionHandler(YES); // After this line, SSL connection will be established
}
I was having the same issue, after i updated my XMPPFramework. After days of trying to find out what went wrong i came across this question, but the solution didn't work for me.
Here is what worked for me. The problem seems to originate from your xmppStream.startTLSPolicy. Setting startTLSPolicy explicitly worked for me.
xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyPreferred; // or
xmppStream.startTLSPolicy = XMPPStreamStartTLSPolicyRequired;
Here is an EXPLANATION of why it works.
In XMPPStream's handleStreamFeatures method, it turns out that. If your XMPP Server doesn't return starttls as 'required' and you don't set startTLSPolicy(default=XMPPStreamStartTLSPolicyAllowed) explicitly. The client will just do a normal connection and not a TLS one.
Here is section of code(for reference) in XMPPStream that is doing the checks.
/**
* This method is called anytime we receive the server's stream features.
* This method looks at the stream features, and handles any requirements so communication can continue.
**/
- (void)handleStreamFeatures
{
NSAssert(dispatch_get_specific(xmppQueueTag), #"Invoked on incorrect queue");
XMPPLogTrace();
// Extract the stream features
NSXMLElement *features = [rootElement elementForName:#"stream:features"];
// Check to see if TLS is required
// Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found)
NSXMLElement *f_starttls = [features elementForName:#"starttls" xmlns:#"urn:ietf:params:xml:ns:xmpp-tls"];
if (f_starttls)
{
if ([f_starttls elementForName:#"required"] || [self startTLSPolicy] >= XMPPStreamStartTLSPolicyPreferred)
{
// TLS is required for this connection
// Update state
state = STATE_XMPP_STARTTLS_1;
// Send the startTLS XML request
[self sendStartTLSRequest];
// We do not mark the stream as secure yet.
// We're waiting to receive the <proceed/> response from the
// server before we actually start the TLS handshake.
// We're already listening for the response...
return;
}
}
else if (![self isSecure] && [self startTLSPolicy] == XMPPStreamStartTLSPolicyRequired)
{
// We must abort the connection as the server doesn't support our requirements.
NSString *errMsg = #"The server does not support startTLS. And the startTLSPolicy is Required.";
NSDictionary *info = [NSDictionary dictionaryWithObject:errMsg forKey:NSLocalizedDescriptionKey];
otherError = [NSError errorWithDomain:XMPPStreamErrorDomain code:XMPPStreamUnsupportedAction userInfo:info];
// Close the TCP connection.
[self disconnect];
// The socketDidDisconnect:withError: method will handle everything else
return;
}
// Check to see if resource binding is required
// Don't forget about that NSXMLElement bug you reported to apple (xmlns is required or element won't be found)
NSXMLElement *f_bind = [features elementForName:#"bind" xmlns:#"urn:ietf:params:xml:ns:xmpp-bind"];
if (f_bind)
{
// Start the binding process
[self startBinding];
// We're already listening for the response...
return;
}
// It looks like all has gone well, and the connection should be ready to use now
state = STATE_XMPP_CONNECTED;
if (![self isAuthenticated])
{
[self setupKeepAliveTimer];
// Notify delegates
[multicastDelegate xmppStreamDidConnect:self];
}
}
You are trying to use outdated API, check iPhoneXMPP sample for the new one - https://github.com/robbiehanson/XMPPFramework/commit/73f3c35a930b91d27e62bc19e91d9cdcc02c6e42
customCertEvaluation = YES;
allowSelfSignedCertificates = YES;
allowSSLHostNameMismatch = NO;
try these this might help
Related
I’m working on an app that uses MPC. Sometimes it's working, A and B client connects like a charm but sometimes connection fails, I get the weird error from MCNearbyServiceBrowser.
First of all, I initialize advertiser, browser, and session on both A and B devices.
_peerID = [[MCPeerID alloc] initWithDisplayName:uniqueId];
session = [[MCSession alloc] initWithPeer:_peerID securityIdentity:nil encryptionPreference:MCEncryptionNone];
session.delegate = self;
NSDictionary *dict = #{#“uniqueId” : uniqueId};
_advertiser = [[MCNearbyServiceAdvertiser alloc] initWithPeer:_peerID discoveryInfo:dict serviceType:#“my-app”];
_advertiser.delegate = self;
_browser = [[MCNearbyServiceBrowser alloc] initWithPeer:_peerID serviceType:#“my-app”];
_browser.delegate = self;
[_advertiser startAdvertisingPeer];
[_browser startBrowsingForPeers];
A and B have a unique ID for deciding what device should invite the other, and what device should accept the invitation (it's necessary to prevent A and B inviting each other at the same time). After they found each other, found peer MCNearbyServiceBrowser delegate called. A device has less uniqueId, and it sends invitation request.
-(void)browser:(MCNearbyServiceBrowser *)browser foundPeer:(MCPeerID *)peerID withDiscoveryInfo:(NSDictionary<NSString *,NSString *> *)info {
if (![[session connectedPeers] containsObject:peerID]) {
NSInteger targetUniqueId = [[peerID displayName] integerValue];
NSInteger myUniqueId = [uniqueId integerValue];
if(myUniqueId<targetUniqueId){
NSLog(#“invitation sent”);
[browser invitePeer:peerID toSession:session withContext:nil timeout:inviteTimeout];
}
}
}
Accepting invitation (this called on B device):
-(void)advertiser:(MCNearbyServiceAdvertiser *)advertiser didReceiveInvitationFromPeer:(MCPeerID *)peerID withContext:(NSData *)context invitationHandler:(void (^)(BOOL, MCSession * _Nonnull))invitationHandler {
NSInteger targetUniqueId = [[peerID displayName] integerValue];
NSInteger myUniqueId = [uniqueId integerValue];
if(myUniqueId>targetUniqueId){
NSLog(#“accepting invitation”);
invitationHandler(YES, session);
}
}
Also implemented certificate handler like this (some post complaining about it, when not implemented it can cause connection problems w/o using security identity too):
-(void)session:(MCSession *)session didReceiveCertificate:(NSArray *)certificate fromPeer:(MCPeerID *)peerID certificateHandler:(void (^)(BOOL))certificateHandler {
certificateHandler(YES);
}
I logged both devices, then:
device A: invitation sent
device B: accepting invitation
device A: [MCNearbyServiceBrowser] Received an invitation response from [3362,090D4987], but we never sent it an invitation. Aborting!
Few secs after, when not connected I stop browsing peers, then start browsing again. After finding peer called I make same connection try again, re-invite peer on device B, what's accepting the invitation. The result can be the same or the connection state switches to Connected. These are the 2 options. Sometimes devices can connect in the first try or in less than 3 tries, but sometimes after many tries. Last time they could connect after about 40 abort message, it took about 15 mins when connection got established.
What I am doing wrong, why device A don't know anything about his own invitation?
An MCPeerID has a hashvalue member. You can compare them directly.
Two MCPeerID objects created using the same display name will not have the same hash value. This is to prevent name collisions.
If you want to recognize and be recognized by previously connected peers you must save and restore the actual MCPeerID objects.
Paste the following code into a playground and run it to see what I mean.
import MultipeerConnectivity
let hostName = "TestPlaygroundHostName"
let firstPeerID = MCPeerID(displayName: hostName)
let secondPeerID = MCPeerID(displayName: hostName)
firstPeerID.hashValue == secondPeerID.hashValue
I am making an app to send UDP packets in order to switch on a LED bulb. I have been able to perform all the actions when I am connecting to the Ad-hoc created by the Wifi bridge.
Now, I want to configure the Wifi bridge so that it can connect to my main router. I have the AT command set to perform this procedure but somehow I am not able to receive the response form the Wifi bridge for the commands which I am sending to it.
The procedure is as follows:-
Step 1 : Send UDP message to the LAN broadcast IP address of "10.10.100.255" and port of 48899 => "Link_Wi-Fi"
All Wifi bridges on the LAN will respond with their details. Response is "10.10.100.254, ACCF232483E8"
Step 2 : (optional for changing settings on the wifi bridge): Then send "+ok" to the LimitlessLED Wifi Bridge. Send UDP message to the response IP address returned from step 1 "10.10.100.254" => "+ok"
Step 3 : (optional for changing settings on the wifi bridge): After that you may send AT commands (ending with \r\n) to the module.
The code for sending the UDP packets is as follows
-(void)configureWifi{
counter++;
NSString *host = #"10.10.100.255";
if ([host length] == 0)
{
[self logError:#"Address required"];
return;
}
int port = 48899; //[portField.text intValue];
if (port <= 0 || port > 65535)
{
[self logError:#"Valid port required"];
return;
}
NSString *msg = #"Link_Wi-Fi";
NSData *data = [msg dataUsingEncoding:NSUTF8StringEncoding];
NSLog(#"the message sent is %#", data);
[udpSocket sendData:data toHost:host port:port withTimeout:-1 tag:tag];
}
Now in order to setup the socket and to receive the data I am using these two delegate methods:
- (void)setupSocket
{
// Setup our socket.
// The socket will invoke our delegate methods using the usual delegate paradigm.
// However, it will invoke the delegate methods on a specified GCD delegate dispatch queue.
//
// Now we can configure the delegate dispatch queues however we want.
// We could simply use the main dispatc queue, so the delegate methods are invoked on the main thread.
// Or we could use a dedicated dispatch queue, which could be helpful if we were doing a lot of processing.
//
// The best approach for your application will depend upon convenience, requirements and performance.
//
// For this simple example, we're just going to use the main thread.
udpSocket = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
NSError *error = nil;
if (![udpSocket bindToPort:0 error:&error])
{
[self logError:FORMAT(#"Error binding: %#", error)];
return;
}
if (![udpSocket beginReceiving:&error])
{
[self logError:FORMAT(#"Error receiving: %#", error)];
return;
}
[self logInfo:#"Ready"];
}
and to Receive data this is the method which is note getting called after sending the UDP packets. This is the delegate method of the GCDAsyncUdpSocket class which I have used in my project in order to send and receive the UDP packets.
- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data
fromAddress:(NSData *)address
withFilterContext:(id)filterContext
{
NSString *msg = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
if (msg)
{
[self logMessage:FORMAT(#"RECV: %#", msg)];
}
else
{
NSString *host = nil;
uint16_t port = 0;
[GCDAsyncUdpSocket getHost:&host port:&port fromAddress:address];
[self logInfo:FORMAT(#"RECV: Unknown message from: %#:%hu", host, port)];
}
}
Once I am able to receive the response I will be able to send the next AT commands in order to configure the Bridge.
Thanks. Any help will be appreciated.
Here are the troubleshooting steps I recommend that you use :
1- I'm assuming you are using ARC so make sure that your udpSocket variable has a strong reference throughout the asynchronous communication. If it is being freed, then that could explain the absence of a callback.
2- Make sure the communication is really happening the way you think it is. Use a software such as Wireshark to capture the packets being exchanged on the network. This should allow you to confirm that your packets do get sent upon calling sendData: and it will also allow you to confirm whether or not you are getting a reply back.
3- Make sure you are using the GCDAsyncUdpSocket properly. Considering you want to broadcast a message, you shouldn't be calling bindToPort:error: in your setupSocket method. Instead you should be calling enableBroadcast:error:. Considering you also want to receive packets after broadcasting, you should use the connectToHost:onPort:error: method to change the state of the socket to allow for bidirectional communication. After that is done, you can replace your usage of sendData:toHost:port:withTimeout:tag: by sendData:withTimeout:tag:. Finally, you can call beginReceiving: so that the delegate gets called for any incoming packets.
4- If this still doesn't get you through it, I recommend that you read throughly the documentation of the GCDAsyncUdpSocket which is very well documented.
You can trouble shoot the problem using Wireshark or any network capture tool.
We use to work in similar kind of project where we used Wireshark extensively.
If packet has reached device(Z-Wave ) it will send out some sort of Ack.
this will help to make sure packets are getting out.
In my delegate.m
- (void)setupStream
{
NSAssert(xmppStream == nil, #"Method setupStream invoked multiple times");
customCertEvaluation = YES;
// allowSelfSignedCertificates = YES;
// allowSSLHostNameMismatch = NO; // Setup xmpp stream
//
// The XMPPStream is the base class for all activity.
// Everything else plugs into the xmppStream, such as modules/extensions and delegates.
xmppStream = [[XMPPStream alloc] init];
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
#if !TARGET_IPHONE_SIMULATOR
{
// Want xmpp to run in the background?
//
// P.S. - The simulator doesn't support backgrounding yet.
// When you try to set the associated property on the simulator, it simply fails.
// And when you background an app on the simulator,
// it just queues network traffic til the app is foregrounded again.
// We are patiently waiting for a fix from Apple.
// If you do enableBackgroundingOnSocket on the simulator,
// you will simply see an error message from the xmpp stack when it fails to set the property.
xmppStream.enableBackgroundingOnSocket = YES;
}
#endif
// Setup reconnect
//
// The XMPPReconnect module monitors for "accidental disconnections" and
// automatically reconnects the stream for you.
// There's a bunch more information in the XMPPReconnect header file.
xmppReconnect = [[XMPPReconnect alloc] init];
// XMPPAutoPing *xmppAutoPing = [[XMPPAutoPing alloc] initWithDispatchQueue:dispatch_get_main_queue()];
//xmppAutoPing.pingInterval = 25.f; // default is 60
//xmppAutoPing.pingTimeout = 10.f; // default is 10
//[xmppAutoPing addDelegate:self delegateQueue:dispatch_get_main_queue()];
//[xmppAutoPing activate:self.xmppStream];
// Setup roster
//
// The XMPPRoster handles the xmpp protocol stuff related to the roster.
// The storage for the roster is abstracted.
// So you can use any storage mechanism you want.
// You can store it all in memory, or use core data and store it on disk, or use core data with an in-memory store,
// or setup your own using raw SQLite, or create your own storage mechanism.
// You can do it however you like! It's your application.
// But you do need to provide the roster with some storage facility.
xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] init];
xmppRosterStorage = [[XMPPRosterCoreDataStorage alloc] initWithInMemoryStore];
xmppRoster = [[XMPPRoster alloc] initWithRosterStorage:xmppRosterStorage];
xmppRoster.autoFetchRoster = YES;
xmppRoster.autoAcceptKnownPresenceSubscriptionRequests = YES;
// Setup vCard support
//
// The vCard Avatar module works in conjuction with the standard vCard Temp module to download user avatars.
// The XMPPRoster will automatically integrate with XMPPvCardAvatarModule to cache roster photos in the roster.
xmppvCardStorage = [XMPPvCardCoreDataStorage sharedInstance];
xmppvCardTempModule = [[XMPPvCardTempModule alloc] initWithvCardStorage:xmppvCardStorage];
xmppvCardAvatarModule = [[XMPPvCardAvatarModule alloc] initWithvCardTempModule:xmppvCardTempModule];
// Setup capabilities
//
// The XMPPCapabilities module handles all the complex hashing of the caps protocol (XEP-0115).
// Basically, when other clients broadcast their presence on the network
// they include information about what capabilities their client supports (audio, video, file transfer, etc).
// But as you can imagine, this list starts to get pretty big.
// This is where the hashing stuff comes into play.
// Most people running the same version of the same client are going to have the same list of capabilities.
// So the protocol defines a standardized way to hash the list of capabilities.
// Clients then broadcast the tiny hash instead of the big list.
// The XMPPCapabilities protocol automatically handles figuring out what these hashes mean,
// and also persistently storing the hashes so lookups aren't needed in the future.
//
// Similarly to the roster, the storage of the module is abstracted.
// You are strongly encouraged to persist caps information across sessions.
//
// The XMPPCapabilitiesCoreDataStorage is an ideal solution.
// It can also be shared amongst multiple streams to further reduce hash lookups.
xmppCapabilitiesStorage = [XMPPCapabilitiesCoreDataStorage sharedInstance];
xmppCapabilities = [[XMPPCapabilities alloc] initWithCapabilitiesStorage:xmppCapabilitiesStorage];
xmppCapabilities.autoFetchHashedCapabilities = YES;
xmppCapabilities.autoFetchNonHashedCapabilities = NO;
// Activate xmpp modules
[xmppReconnect activate:xmppStream];
[xmppRoster activate:xmppStream];
[xmppvCardTempModule activate:xmppStream];
[xmppvCardAvatarModule activate:xmppStream];
[xmppCapabilities activate:xmppStream];
// Add ourself as a delegate to anything we may be interested in
[xmppStream addDelegate:self delegateQueue:dispatch_get_main_queue()];
[xmppRoster addDelegate:self delegateQueue:dispatch_get_main_queue()];
// Optional:
//
// Replace me with the proper domain and port.
// The example below is setup for a typical google talk account.
//
// If you don't supply a hostName, then it will be automatically resolved using the JID (below).
// For example, if you supply a JID like 'user#quack.com/rsrc'
// then the xmpp framework will follow the xmpp specification, and do a SRV lookup for quack.com.
//
// If you don't specify a hostPort, then the default (5222) will be used.
[xmppStream setHostName:#"10.10.1.77"];
[xmppStream setHostPort:5222];
// You may need to alter these settings depending on the server you're connecting to
// allowSelfSignedCertificates = YES;
// allowSSLHostNameMismatch = NO;
customCertEvaluation = YES;
}
and also
- (void)xmppStream:(XMPPStream *)sender willSecureWithSettings:(NSMutableDictionary *)settings
{
DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
NSString *expectedCertName = [xmppStream.myJID domain];
if (expectedCertName)
{
[settings setObject:expectedCertName forKey:(NSString *)kCFStreamSSLPeerName];
}
if (customCertEvaluation)
[settings setObject:#(YES) forKey:GCDAsyncSocketManuallyEvaluateTrust];
}
- (void)xmppStream:(XMPPStream *)sender didReceiveTrust:(SecTrustRef)trust
completionHandler:(void (^)(BOOL shouldTrustPeer))completionHandler
{
/*DDLogVerbose(#"%#: %#", THIS_FILE, THIS_METHOD);
// The delegate method should likely have code similar to this,
// but will presumably perform some extra security code stuff.
// For example, allowing a specific self-signed certificate that is known to the app.
allowSelfSignedCertificates = YES;
allowSSLHostNameMismatch = NO;
dispatch_queue_t bgQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(bgQueue, ^{
SecTrustResultType result = kSecTrustResultDeny;
OSStatus status = SecTrustEvaluate(trust, &result);
if (status == noErr && (result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)) {
completionHandler(YES);
}
else {
completionHandler(NO);
}
});
*/
completionHandler(YES);
}
I have done everything that was suggested in code but still connecting to server using SSL port gives error
2014-07-18 18:08:14:724 iPhoneXMPP[20593:60b] iPhoneXMPPAppDelegate: xmppStream:socketDidConnect:
2014-07-18 18:08:14:724 iPhoneXMPP[20593:60b] iPhoneXMPPAppDelegate: xmppStream:socketDidConnect:
2014-07-18 18:08:14:925 iPhoneXMPP[20593:60b] iPhoneXMPPAppDelegate: xmppStreamDidDisconnect:withError:
2014-07-18 18:08:14.925 iPhoneXMPP[20593:60b] Unable to connect to server
2014-07-18 18:08:14:926 iPhoneXMPP[20593:60b] Unable to connect to server. Check xmppStream.hostName
How am i supposed to solve to this error; Connection to normal port is fine though.Connection to SSL port is the only problem.
I finally can use SSL in 5223 port. I have to force use startTLS on didConnectToHost on XMPPStream.m. I don't know why isSecure always says NO.
Use the following method to enable the SSL/TLS. However this method is not defined theXMPPStream.h class, you need to define it here and access it from appdelegate or inside setupsteam method.
-(void)setIsSecure:(BOOL)flag
About the "duplicate" state of this question:
This question here was asked in Nov 2012, it contains a detailed description of the problem and has 3 answers.
The question referred to as "original" was asked in Feb 2013 (3 month after this "duplicate"), has no detailed description and only 2 answers. The best of its two answers is just a link-only-answer.
Why do I get this message in my console?:
purgeIdleCellConnections: found one to purge conn = (some object-ID)
When my App starts I send a message to my server. I do this with this lines of code:
#implementation AppStatus {
NSMutableData* activeDownload;
NSURLConnection* Connection;
}
- (id) init {
self = [super init];
if (self) {
activeDownload = nil;
Connection = nil;
}
return self;
}
- (void)sendStatus:(NSString*)url {
NSString* escaped = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSURLConnection* conn =[[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:escaped]] delegate:self];
Connection = conn;
NSLog(#"%s Connection=%#",__PRETTY_FUNCTION__,Connection);
}
In the same file I have this delegate-methods:
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data {
NSLog(#"%s connection=%#",__PRETTY_FUNCTION__,connection);
[activeDownload appendData:data];
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error {
NSLog(#"%s connection=%#",__PRETTY_FUNCTION__,connection);
activeDownload = nil;
Connection = nil;
//do nothing else; just ignore the error
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection {
NSLog(#"%s Connection=%#",__PRETTY_FUNCTION__,Connection);
NSString* answer = [[NSString alloc] initWithData:activeDownload encoding:NSUTF8StringEncoding];
//do something usefull with the server's answer
activeDownload = nil;
Connection = nil;
}
When i run this app on a real device (not on the simulator), I get this messages in the console:
2012-11-22 20:41:51.309 BookMan[376:907] -[AppStatus sendStatus:] Connection=<NSURLConnection: 0x1dd7ff40>
2012-11-22 20:41:51.929 BookMan[376:907] -[AppStatus connection:didReceiveData:] Connection=<NSURLConnection: 0x1dd7ff40>
2012-11-22 20:41:51.935 BookMan[376:907] -[AppStatus connectionDidFinishLoading:] Connection=<NSURLConnection: 0x1dd7ff40>
purgeIdleCellConnections: found one to purge conn = 0x1dd8ff60
The purgeIdleCellConnections-message apears about 4 or 5 seconds after the connectionDidFinishLoading-message.
As you can see, the object-number of the purgeIdleCellConnections-message is not the same as the number of the connection I did create and use in my app.
Maybe IMPORTANT: I do get this message only when I run the App on a real device (iPhone 4 with iOS 6.0.1). This device uses a 3G-connection, not a WIFI-connection. At the moment I have no WIFI-network to test if this happens on a WIFI-connection too.
I do not get this message when I run the App on the simulator.
When I change the method sendStatus: to this:
- (void)sendStatus:(NSString*)url {
NSString* escaped = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
//NSURLConnection* conn =[[NSURLConnection alloc] initWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:escaped]] delegate:self];
//Connection = conn;
NSLog(#"%s Connection=%#",__PRETTY_FUNCTION__,Connection);
}
I get this output in the console:
2012-11-22 20:45:11.927 BookMan[391:907] -[AppStatus sendStatus:] Connection=(null)
I do not get the purgeIdleCellConnections-message when I do not create a connection.
So what does this message mean and why do I get it?:
purgeIdleCellConnections: found one to purge conn = (some object-ID)
It means that, a connection which has been idle for too long has got its TCP connection closed. This is a basic mechanism, and it's log shouldn't bother you as Apple states on Technical QA1774, it's a debug message that has been, erroneously, left enabled.
This log is, indeed, only shown when WWAN connections are purged.
Yes this the OS closing active connections over cellular data i.e 3G only. You shouldn't see this using a Wifi connection. It would seem that the OS purging the connections adds a bit of time to send data request to a server.
When on the device connected to cellular network, I have observed this debug message coming out of the iOS 6.0 SDK. Timing-wise I find it correlates to 'active' AJAX calls being terminated in my app. However it is very difficult to prove anything since this only occurs when rendering the web page in a UIWebView. I am just saying I don't think the messages are benign. I think they might indicate a bug in the Apple framework that is overly aggressive in terminating connections. It is hard to get instrumentation on the javascript running inside the UIWebView that makes the AJAX calls, so it is all highly speculative at this time.
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 11 years ago.
My application times out while trying to connect to host. The timeout time is set to unlimited so I take it that the client is really just unable to connect at all.
I have an iPad app running asyncsockets and I'm trying to get it to connect to a server on my desktop also using asyncsockets. The iPad is specifically iOS 5 and is using GCD asyncsockets.
The server is being invoked through a NSRunLoop. It receives no form of connection from the client (none of breakpoints are caught like they are for telnet connections).
I'm able to telnet into the server from other machines just fine. I'm also able to connect the iPad client to host:google.com on port:80 just fine.
I've tried connecting the iPad to the server on ports 8080, 4500, and 50000 all to no success (they all work for telnet though).
I believe there is something in the server code causing this but I am not sure.
My server code is from a sample found here: http://mysterycoconut.com/blog/2010/07/tweak-away/
My client code is modified HTTP client code from the sample GCD code supplied by the asyncsockets repository: https://github.com/robbiehanson/CocoaAsyncSocket/blob/master/Examples/GCD/SimpleHTTPClient/Mobile/SimpleHTTPClient/SimpleHTTPClientAppDelegate.m
Here is my server code:
- (id) init;
{
self = [super init];
if (self != nil)
{
debugServer = [[AsyncSocket alloc] initWithDelegate:self];
connectedClients = [[NSMutableArray alloc] initWithCapacity:1];
running = false;
}
return self;
}
- (void) dealloc;
{
[self stop];
[connectedClients release];
[debugServer release];
[super dealloc];
}
- (void) startOnPort
{
if (running) return;
if (_port < 0 || _port > 65535)
_port = 0;
NSError *error = nil;
if (![debugServer acceptOnPort:_port error:&error])
return;
NSLog(#"My Awesome Debug Server has started on port %hu", [debugServer localPort]);
running = true;
}
- (void) stop;
{
if (!running) return;
[debugServer disconnect];
for (AsyncSocket* socket in connectedClients)
[socket disconnect];
running = false;
}
- (void) setPort:(int)in_port{
_port = in_port;
}
- (void)onSocket:(AsyncSocket *)socket didAcceptNewSocket:(AsyncSocket *)newSocket;
{
[connectedClients addObject:newSocket];
}
- (void)onSocketDidDisconnect:(AsyncSocket *)socket;
{
[connectedClients removeObject:socket];
}
- (void)onSocket:(AsyncSocket *)socket didConnectToHost:(NSString *)host port:(UInt16)port;
{
NSLog(#"Accepted client %#:%hu", host, port);
NSData *welcomeData = [#"Welcome to my Awesome Debug Server\r\n\r\n"
dataUsingEncoding:NSUTF8StringEncoding];
[socket writeData:welcomeData withTimeout:-1 tag:WelcomeMsgTag];
[socket readDataWithTimeout:-1 tag:GenericMsgTag];
}
- (void)onSocket:(AsyncSocket *)socket didReadData:(NSData *)data withTag:(long)tag;
{
NSString *tmp = [NSString stringWithUTF8String:[data bytes]];
NSString *input = [tmp stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"%#",input);
if ([input isEqualToString:#"exit"])
{
NSData *byeData = [#"Bye!\r\n" dataUsingEncoding:NSUTF8StringEncoding];
[socket writeData:byeData withTimeout:-1 tag:GenericMsgTag];
[socket disconnectAfterWriting];
return;
}
[socket readDataWithTimeout:-1 tag:GenericMsgTag];
}
#end
...and here is my client code:
- (id) init
{
if (self = [super init]) {
// AsyncSocket optionally uses the Lumberjack logging framework.
//
// Lumberjack is a professional logging framework. It's extremely fast and flexible.
// It also uses GCD, making it a great fit for GCDAsyncSocket.
//
// As mentioned earlier, enabling logging in GCDAsyncSocket is entirely optional.
// Doing so simply helps give you a deeper understanding of the inner workings of the library (if you care).
// You can do so at the top of GCDAsyncSocket.m,
// where you can also control things such as the log level,
// and whether or not logging should be asynchronous (helps to improve speed, and
// perfect for reducing interference with those pesky timing bugs in your code).
//
// There is a massive amount of documentation on the Lumberjack project page:
// https://github.com/CocoaLumberjack/CocoaLumberjack
//
// But this one line is all you need to instruct Lumberjack to spit out log statements to the Xcode console.
[DDLog addLogger:[DDTTYLogger sharedInstance]];
// Create our GCDAsyncSocket instance.
//
// Notice that we give it the normal delegate AND a delegate queue.
// The socket will do all of its operations in a background queue,
// and you can tell it which thread/queue to invoke your delegate on.
// In this case, we're just saying invoke us on the main thread.
// But you can see how trivial it would be to create your own queue,
// and parallelize your networking processing code by having your
// delegate methods invoked and run on background queues.
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
// Now we tell the ASYNCHRONOUS socket to connect.
//
// Recall that GCDAsyncSocket is ... asynchronous.
// This means when you tell the socket to connect, it will do so ... asynchronously.
// After all, do you want your main thread to block on a slow network connection?
//
// So what's with the BOOL return value, and error pointer?
// These are for early detection of obvious problems, such as:
//
// - The socket is already connected.
// - You passed in an invalid parameter.
// - The socket isn't configured properly.
//
// The error message might be something like "Attempting to connect without a delegate. Set a delegate first."
//
// When the asynchronous sockets connects, it will invoke the socket:didConnectToHost:port: delegate method.
NSError *error = nil;
#if USE_SECURE_CONNECTION
uint16_t port = 443; // HTTPS
#else
uint16_t port = 8080; // HTTP
#endif
DDLogVerbose(#"port: %d\t host: %#",port,#"130.85.92.12");
if (![asyncSocket connectToHost:#"130.85.92.12" onPort:port error:&error])
{
DDLogError(#"Unable to connect to due to invalid configuration: %#", error);
}
else
{
DDLogVerbose(#"Connecting...");
}
#if USE_SECURE_CONNECTION
// The connect method above is asynchronous.
// At this point, the connection has been initiated, but hasn't completed.
// When the connection is establish, our socket:didConnectToHost:port: delegate method will be invoked.
//
// Now, for a secure connection we have to connect to the HTTPS server running on port 443.
// The SSL/TLS protocol runs atop TCP, so after the connection is established we want to start the TLS handshake.
//
// We already know this is what we want to do.
// Wouldn't it be convenient if we could tell the socket to queue the security upgrade now instead of waiting?
// Well in fact you can! This is part of the queued architecture of AsyncSocket.
//
// After the connection has been established, AsyncSocket will look in it's queue for the next task.
// There it will find, dequeue and execute our request to start the TLS security protocol.
//
// The options passed to the startTLS method are fully documented in the GCDAsyncSocket header file.
// The deusty server only has a development (self-signed) X.509 certificate.
// So we tell it not to attempt to validate the cert (cause if it did it would fail).
NSDictionary *options = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO]
forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
[asyncSocket startTLS:options];
#endif
}
return self;
}
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
DDLogVerbose(#"socket:didConnectToHost:%# port:%hu", host, port);
// HTTP is a really simple protocol.
//
// If you don't already know all about it, this is one of the best resources I know (short and sweet):
// http://www.jmarshall.com/easy/http/
//
// We're just going to tell the server to send us the metadata (essentially) about a particular resource.
// The server will send an http response, and then immediately close the connection.
NSString *msg = #"iOS client connected\r\n\r\n";
NSData *msgdata = [msg dataUsingEncoding:NSUTF8StringEncoding];
[asyncSocket writeData:msgdata withTimeout:-1.0 tag:0];
// Side Note:
//
// The AsyncSocket family supports queued reads and writes.
//
// This means that you don't have to wait for the socket to connect before issuing your read or write commands.
// If you do so before the socket is connected, it will simply queue the requests,
// and process them after the socket is connected.
// Also, you can issue multiple write commands (or read commands) at a time.
// You don't have to wait for one write operation to complete before sending another write command.
//
// The whole point is to make YOUR code easier to write, easier to read, and easier to maintain.
// Do networking stuff when it is easiest for you, or when it makes the most sense for you.
// AsyncSocket adapts to your schedule, not the other way around.
#if READ_HEADER_LINE_BY_LINE
// Now we tell the socket to read the first line of the http response header.
// As per the http protocol, we know each header line is terminated with a CRLF (carriage return, line feed).
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
#else
// Now we tell the socket to read the full header for the http response.
// As per the http protocol, we know the header is terminated with two CRLF's (carriage return, line feed).
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
#endif
}
- (void)socketDidSecure:(GCDAsyncSocket *)sock
{
// This method will be called if USE_SECURE_CONNECTION is set
DDLogVerbose(#"socketDidSecure:");
}
- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
DDLogVerbose(#"socket:didWriteDataWithTag:");
}
- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
DDLogVerbose(#"socket:didReadData:withTag:");
NSString *httpResponse = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#",httpResponse);
#if READ_HEADER_LINE_BY_LINE
DDLogInfo(#"Line httpResponse: %#", httpResponse);
// As per the http protocol, we know the header is terminated with two CRLF's.
// In other words, an empty line.
if ([data length] == 2) // 2 bytes = CRLF
{
DDLogInfo(#"<done>");
}
else
{
// Read the next line of the header
[asyncSocket readDataToData:[GCDAsyncSocket CRLFData] withTimeout:-1.0 tag:0];
}
#else
DDLogInfo(#"Full httpResponse: %#", httpResponse);
#endif
[httpResponse release];
}
- (void)socketDidDisconnect:(GCDAsyncSocket *)sock withError:(NSError *)err
{
// Since we requested HTTP/1.0, we expect the server to close the connection as soon as it has sent the response.
DDLogVerbose(#"socketDidDisconnect:withError: \"%#\"", err);
}
I've looked around for answers but have had no success. I figured the best course of action would be to ask you all rather than wrack my brain trying to solve it myself.
The solution was to connect to the local network differently. I was operating with a network that required a log in for local access but offered a "visitors" connection for wide area network access. The device (iPad) was automatically connecting as a "visitor" and I needed to log in manually.
So, if you aren't able to connect w/ this API, check out how your connecting to the network!