Just An Application

November 22, 2013

Service Discovery In Android And iOS: Part Nine – iOS Take Five

And if none of the previous choices are to your liking then there is also the theoretical possibility of doing the whole thing from scratch as in the Java case.

For this to be feasible it must be possible to send and receive multicast UDP datagrams at the application level. There is nothing in Foundation or Core Foundation that supports this directly but it is possible at the POSIX level.

1.0 Opening A Multicast UDP Socket Using POSIX System Calls

Opening a multicast UDP socket at the POSIX level is feasible if a little bit fiddly as befits something that needs to be done in C/C++.

It requires three system calls.

1.1 socket

First we need to open a socket using the socket system call which is declared in the header file <sys/socket.h>
like this

    int socket(int domain, int type, int protocol);

In our case the domain argument is AF_INET because we want to use the socket to communicate using IP over the network.

The type argument is SOCK_DGRAM because we want to use the socket to send and receive datagrams.

The protocol argument is 0 which means we want to use the default protocol given the socket domain and type which should be UDP.

So our code to open the socket looks like this.

    ...
    
    s = socket(AF_INET, SOCK_DGRAM, 0);
    if (s == -1)
    {
        perror("socket");
        return -1;
    }

    ...

1.2 bind

Having opened the socket we need to associate it with a local address and port using the bind system call which is declared in the header file <sys/socket.h> like this

    int bind(int socket, const struct sockaddr *address, socklen_t address_len);

In our case we need to specify an IP address using an instance of struct sockaddr_in which is defined in the header file
<netinet/in.h> and then cast appropriately.

On the assumption that there is only one network interface so we do not need to be bound to a specific one we specify the address as INADDR_ANY.

As we do not need to send and receive on a specific port we specify the port as 0 so the system will allocate a port number for us.

    ...
    
    struct sockaddr_in inetAddress;
    
    memset(&inetAddress, 0, sizeof(inetAddress));
    
    inetAddress.sin_family      = AF_INET;
    inetAddress.sin_addr.s_addr = htonl(INADDR_ANY);
    inetAddress.sin_port        = htons(0);
    
    
    if (bind(s, (const struct sockaddr*)&inetAddress, sizeof(inetAddress)) == -1)
    {
        perror("bind");
        close(s);
        return -1;
    }

    ...

Note

The address and port in the sockaddr_in struct should be in network byte order hence the use of htonl and htons.

As it happens in this case it is not necessary to convert either the address or port from host to network byte order but doing so in all cases helps ensure that you don’t forget in a case where it is necessary.

1.3 setsockopt

Having bound the socket to a local address and port number we need to associate it with the multicast group we wish to send to and receive from.

We do this using the setsockopt system call which is defined like this in the header file <sys/socket.h>

    int setsockopt(int socket, int level, int option_name, const void *option_value, socklen_t option_len);

In our case level is IPPROTO_IP as we are setting an IP level option.

The option_name is IP_ADD_MEMBERSHIP indicating that we want to add the socket to the membership of a multicast group.

The option_value is a pointer to an instance of the struct ip_mreq which specifies the group address,
imr_multiaddr, and the network interface, imr_interface.

    ...

    struct ip_mreq imr;
    
    imr.imr_multiaddr.s_addr = group;
    imr.imr_interface.s_addr = htonl(INADDR_ANY);
    
    if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) == -1)
    {
        perror("setsockopt");
        close(s);
        return -1;
    }
    
    ...

where group is the multicast group address in network byte order.

As before we assume there is only a single network interface and specify INADDR_ANY.

Note

The imr_multiaddr and imr_interface fields are both of type struct in_addr and as in the bind case above the addresses must be in network byte order.

2.0 Making A POSIX Socket More iOS Like

Putting to one side for the moment the issue of whether it is actually possible to open a multicast socket there is the question of how it could be used in the context of an iOS application.

Although a POSIX socket is perfectly functional, receiving datagrams using the recvfrom system call is by default synchronous so using it as is would require at least one separate non-standard thread.

Fortunately the function CFSocketCreateWithNative can turn a theoretical POSIX socket into a theoretical CFSocket.

Using CFSocketCreateWithNative we can specify a function to be called whenever the socket is readable and in return we will get a CFSocketRef

Once we have a theoretical CFSocketRef we can obtain a CFRunLoopSource using the function CFSocketCreateRunLoopSource and then add it to the CFRunLoop of our choice.

The end result will be that our function is called when the socket is readable so recvfrom will not block and this will be done in the context of a standard CFRunLoop.

3.0 But Does It Work ?

Perhaps surprisingly the answer is yes, at least on an iPad running iOS 7.0.4.

DNS messages can be multicast, responses received and services duly discovered.

4.0 The MulticastSocket Class

Here is an initial attempt at encapsulating the multicast socket.

    //
    //  MulticastSocket.m
    //  XperTakeFive
    //
    //  Created by Simon Lewis on 03/11/2013.
    //  Copyright (c) 2013 Simon Lewis. All rights reserved.
    //
    
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    
    #import "MulticastSocket.h"
    
    @interface MulticastSocket()
    
    - (void)read;
    
    @end
    
    @implementation MulticastSocket
    {
        CFSocketRef                 socketRef;
        CFRunLoopSourceRef          runLoopSourceRef;
    
        in_addr_t                   group;
        int                         s;
    
        struct sockaddr_in          from;
        unsigned char               buffer[4096];
    }
    
    static void socketCallback(
                    CFSocketRef          theSocketRef,
                    CFSocketCallBackType theCallbackType,
                    CFDataRef            theAddress,
                    const void*          theData,
                    void*                theInfo)
    {
        switch (theCallbackType)
        {
            case kCFSocketReadCallBack:
    
                [(__bridge MulticastSocket*)theInfo read];
                break;
    
            default:
    
                NSLog(@"socketCallback: %lu !", theCallbackType);
        }
    }
    
    - (id)init:(NSString*)theGroup
    {
        self = [super init];
        if (self != nil)
        {
            group = inet_addr([theGroup UTF8String]);
        }
        return self;
    }
    
    - (void)dealloc
    {
        if (s != -1)
        {
            close(s);
            CFRelease(socketRef);
            CFRelease(runLoopSourceRef);
        }
    }
    
    - (BOOL)open
    {
        s = socket(AF_INET, SOCK_DGRAM, 0);
        if (s == -1)
        {
            perror("socket");
            return NO;
        }
    
        struct sockaddr_in inetAddress;
    
        memset(&inetAddress, 0, sizeof(inetAddress));
    
        inetAddress.sin_family      = AF_INET;
        inetAddress.sin_addr.s_addr = htonl(INADDR_ANY);
        inetAddress.sin_port        = htons(0);
    
    
        if (bind(s, (const struct sockaddr*)&inetAddress, sizeof(inetAddress)) == -1)
        {
            perror("bind");
            return NO;
        }
    
        struct ip_mreq imr;
    
        imr.imr_multiaddr.s_addr = group; // in network byte order courtesy of inet_addr
        imr.imr_interface.s_addr = htonl(INADDR_ANY);
    
        if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(imr)) == -1)
        {
            perror("setsockopt");
            return NO;
        }
    
        CFSocketContext context;
    
        memset(&context, 0, sizeof(context));
    
        context.info = (__bridge void*)self;
    
        socketRef = CFSocketCreateWithNative(NULL, s, kCFSocketReadCallBack, socketCallback, &context);
        if (socketRef == NULL)
        {
            return NO;
        }
        runLoopSourceRef = CFSocketCreateRunLoopSource( NULL, socketRef, 0);
        if (runLoopSourceRef == NULL)
        {
            return NO;
        }
        CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSourceRef, kCFRunLoopDefaultMode);
        return YES;
    }
    
    - (void)send:(unsigned char*)theData ofLength:(ssize_t)theLength toPort:(uint16_t)thePort
    {
        struct sockaddr_in inetAddress;
    
        memset(&inetAddress, 0, sizeof(inetAddress));
    
        inetAddress.sin_family      = AF_INET;
        inetAddress.sin_addr.s_addr = group;
        inetAddress.sin_port        = ntohs(thePort);
    
        ssize_t nBytes = sendto(s, theData, theLength, 0, (const struct sockaddr*)&inetAddress, sizeof(inetAddress));
    
        if (nBytes == -1)
        {
            perror("sendto");
        }
    }
    
    // internal
    
    - (void)read
    {
        socklen_t length = sizeof(from);
    
        memset(&from, 0, sizeof(from));
    
        ssize_t nBytes = recvfrom(s, buffer, sizeof(buffer), 0, (struct sockaddr*)&from, &length);
    
        if (nBytes != -1)
        {
            [self.delegate received:buffer ofLength:nBytes from:&from];
        }
        else
        {
            perror("recvfrom");
        }
    }
    
    @end

5.0 And The Rest

The rest is just reading and writing messages which is much the same in Objective-C as it is in Java.

For example, here is the method for reading a message.

    ...
    
    - (BOOL)readRecord:(Category)theCategory
    {
        NSString* name = [self readNodeName];
    
        if (name == nil)
        {
            return NO;
        }
        if ((offset + HEADER_LENGTH) > length)
        {
            return NO;
        }
    
        uint16_t type     = UNSIGNED_SHORT;
        uint16_t class    = UNSIGNED_SHORT;
        uint32_t ttl      = UNSIGNED_INT;
        uint16_t rdlength = UNSIGNED_SHORT;
    
        [responseHandler
             record:
                 [[ResourceRecord alloc]
                       init:
                           name
                       type:
                           type
                       class:
                           class
                       ttl:
                           ttl
                       data:
                           [[RecordData alloc]
                                 init:
                                     message
                                 offset:
                                     offset
                                 length:
                                     offset + rdlength]]
             category:
                 theCategory];
        offset += rdlength;
        return YES;
    }

    ...

Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Advertisements

November 14, 2013

Service Discovery In Android And iOS: Part Six – iOS Take Two

At the Core Foundation level we have the CFNetServices API which supports the registration and discovery of services using DNS/mDNS.

1.0 CFNetServiceBrowser

A CFNetServiceBrowser is the CFNetServices equivalent of an NSNetServiceBrowser.

2.0 CFNetService

A CFNetService is the CFNetServices equivalent of an NSNetService.

3.0 Synchronous vs. Aynchronous

Unlike the NSNetServiceBrowser and NSNetService methods the equivalent CFNetServiceBrowser and CFNetService functions can be used synchronously or asynchronously.

By default the functions are synchronous.

To make them asynchronous for a given CFNetServiceBrowser or CFNetService it must be added to a CFRunLoop.

4.0 Searching For Services Asynchronously

To search for services of a given type using the CFNetworkServices API we need to

  1. create a CFNetServiceBrowser

  2. make it asynchronous

  3. start the search

4.1 CFNetServiceBrowserCreate

We can create a CFNetServiceBrowser using the function CFNetServiceBrowserCreate which is declared like this

    CFNetServiceBrowserRef CFNetServiceBrowserCreate (
                               CFAllocatorRef                    alloc,
                               CFNetServiceBrowserClientCallBack clientCB,
                               CFNetServiceClientContext*        clientContext);

The alloc argument is a reference to an allocator to use when creating the CFNetServiceBrowser or more likely the constant

    kCFAllocatorDefault

4.1.1 The clientCB Argument

The clientCB argument is a pointer to a function of type CFNetServiceBrowserClientCallBack.

This function will be invoked when a service is found or if an error occurs.

The function type CFNetServiceBrowserClientCallBack is declared like this

    typedef void (*CFNetServiceBrowserClientCallBack) (
                       CFNetServiceBrowserRef browser,
                       CFOptionFlags          flags,
                       CFTypeRef              domainOrService,
                       CFStreamError*         error,
                       void*                  info);;

The browser argument is the CFNetServiceBrowserRef that was returned from the call to CFNetServiceBrowserCreate.

The callback function is also invoked when searching for domains so the flags argument is used to distinguish between the two uses.

It is also used to distinguish between the discovery of a service and the disappearance of a previously discovered service.

When a service is discovered the value of the flags argument will be zero (0).

When a previously discovered service disappears the value of the flags argument will be

   kCFNetServiceFlagRemove

When being invoked on the discovery or disappearance of a service the domainOrService argument will be a
CFNetServiceRef.

If an error has occurred, the error and domain fields of error argument will be set.

The info argument is the value of the info field of the CFNetServiceClientContext passed to the call to CFNetServiceBrowserCreate.

4.1.2 The clientContext Argument

The clientContext argument is a pointer to a CFNetServiceClientContext which is declared like this.

    struct CFNetServiceClientContext {
        CFIndex                            version;
        void*                              info;
        CFAllocatorRetainCallBack          retain;
        CFAllocatorReleaseCallBack         release;
        CFAllocatorCopyDescriptionCallBack copyDescription;
    };
        
    typedef struct CFNetServiceClientContext CFNetServiceClientContext;

The info field can be used to store a pointer to an ‘object’ which will be passed to the callback function passed via the clientCB argument.

If the retain and release fields are not NULL then the functions specified will be invoked when the implementation wishes to retain and release the ‘object’ specified in the info field.

It is not clear from the documentation whether the contents of the CFNetServiceClientContext are copied.

A const qualifier is sometimes a clue, but a little experimentation shows that they are, so it is safe to stack allocate.

4.2 CFNetServiceBrowserScheduleWithRunLoop

We can add our newly created CFNetServiceBrowser to a CFRunLoop using the function CFNetServiceBrowserScheduleWithRunLoop which is declared like this

    void CFNetServiceBrowserScheduleWithRunLoop(
             CFNetServiceBrowserRef browser,
             CFRunLoopRef           runLoop,
             CFStringRef            runLoopMode);

The easiest way to use this is to add the CFNetServiceBrowser to the main CFRunLoop in the default mode like so

    CFNetServiceBrowserScheduleWithRunLoop(
        browser, 
        CFRunLoopGetMain(), 
        kCFRunLoopDefaultMode);

4.3 CFNetServiceBrowserSearchForServices

Once we have made our CFNetServiceBrowse asynchronous we can start the search for services by calling the function CFNetServiceBrowserSearchForServices which is declared like this

    Boolean CFNetServiceBrowserSearchForServices (
                CFNetServiceBrowserRef browser,
                CFStringRef            domain,
                CFStringRef            serviceType,
                CFStreamError*         error);

The browser argument should be the CFNetServiceBrowserRef returned from the call to CFNetServiceBrowserCreate.

The domain argument should be the absolute name of the domain in which to search, e.g.,

    "local."

The serviceType argument should be the domain relative type of the services to search for, e.g.,

    "_ipp._tcp."

The documentation for the error argument is a tad confusing.

It states

A pointer to a CFStreamError structure, that, if an error occurs, will be set to the error and the error’s domain and passed to your callback function.

The

… and passed to your callback

bit does not appear to be true.

If it is not possible to start the search then the function returns false immediately and the domain and error fields of the CFStreamError are set.

5.0 Resolving A Service Asynchronously

Once a service has been found we need to resolve it …

To do this asynchronously we need to

  1. make it possible to get the result asynchronously

  2. make the CFNetService asynchronous

  3. start the resolution

5.1 CFNetServiceSetClient

To get the results from CFNetService functions when running asynchronously we must associate a callback function and a context with the CFNetService first.

We do this using the function CFNetServiceSetClient which is declared like this

    Boolean CFNetServiceSetClient(
                CFNetServiceRef            theService,
                CFNetServiceClientCallBack clientCB,
                CFNetServiceClientContext* clientContext);

5.1.1 The clientCB Argument

The clientCB argument is a pointer to a function of type CFNetServiceClientCallBack

This function will be invoked when the service is resolved or an error occurs.

The function type CFNetServiceClientCallBack is declared like this

    typedef void (*CFNetServiceClientCallBack) (
                      CFNetServiceRef theService,
                      CFStreamError*  error,
                      void*           info);

The info argument is the value of the info field of the CFNetServiceClientContext passed as the clientContext argument in the call to the CFNetServiceSetClient

If an error has occurred, the error and domain fields of error argument will be set.

5.1.2 The clientContext Argument

The clientContext argument is a pointer to a CFNetServiceClientContext which we have already seen used with the function CFNetServiceBrowserCreate.

5.2 CFNetServiceScheduleWithRunLoop

We can add our CFNetService instance to a CFRunLoop using the function CFNetServiceScheduleWithRunLoop which is declared like this.

    void CFNetServiceScheduleWithRunLoop(
             CFNetServiceRef theService,
             CFRunLoopRef    runLoop,
             CFStringRef     runLoopMode);

The easiest way to use this is to add the CFNetService to the main CFRunLoop in the default mode like so

    CFNetServiceScheduleWithRunLoop(service, CFRunLoopGetMain(), kCFRunLoopDefaultMode);

5.3 CFNetServiceResolveWithTimeout

Once we have made our CFNetService asynchronous we can start the resolution bey calling the function
CFNetServiceResolveWithTimeout which is declared like this

    Boolean CFNetServiceResolveWithTimeout(
                CFNetServiceRef theService,
                CFTimeInterval  timeout,
                CFStreamError*  error);

The timeout argument specifies the amount of time in seconds that the implementation should wait for the resolution to complete If the value is less than or equal to zero the implementation will wait indefinitely.

If it not possible to start the resolution the function returns false immediately and the domain and error fields of the CFStreamError argument are set.

6.0 The FindServices Class

Here is the FindServices class re-written to use the CFNetServices API.

    //
    //  FindServices.m
    //  XperTakeTwo
    //
    //  Created by Simon Lewis on 12/11/2013.
    //  Copyright (c) 2013 Simon Lewis. All rights reserved.
    //
    
    #import "Service.h"
    
    #import "FindServices.h"
    
    @interface FindServices ()
    
    @property NSString*             type;
    @property NSString*             domain;
    
    @property NSMutableDictionary*  services;
    
    
    - (void)serviceFound:(CFNetServiceRef)theService;
    
    - (void)serviceLost:(CFNetServiceRef)theService;
    
    
    - (void)resolved:(CFNetServiceRef)theService;
    
    - (void)resolveFailed:(CFNetServiceRef)theService withError:(CFStreamError*)theError;
    
    
    - (void)stopBrowser;
    
    - (void)stopService:(CFNetServiceRef)theService;
    
    - (void)log:(NSString*)theMessage service:(CFNetServiceRef)theService;
    
    @end
    
    @implementation FindServices
    {
        CFNetServiceBrowserRef      browser;
    }
    
    - (FindServices*)initWithType:(NSString*)theType andDomain:(NSString*)theDomain
    {
        self = [super init];
        if (self != nil)
        {
            self.type     = theType;
            self.domain   = theDomain;
            self.services = [NSMutableDictionary dictionaryWithCapacity:8];
        }
        return self;
    }
    
    
    
    static void browserCallBack(
                    CFNetServiceBrowserRef  theBrowser,
                    CFOptionFlags           theFlags,
                    CFTypeRef               theDomainOrService,
                    CFStreamError*          theError,
                    void*                   theInfo)
    {
        NSLog(@"browserCallBack");
    
        if ((theError->error) != 0)
        {
            NSLog(@"error: %d\n", (int)theError->error);
        }
        else
        if ((theFlags & kCFNetServiceFlagIsDomain) != 0)
        {
            NSLog(@"domain !\n");
        }
        else // service
        if ((theFlags & kCFNetServiceFlagRemove) == 0)
        {
            [(__bridge FindServices*)theInfo serviceFound:(CFNetServiceRef)theDomainOrService];
        }
        else
        {
            [(__bridge FindServices*)theInfo serviceLost:(CFNetServiceRef)theDomainOrService];
        }
    }
    
    - (void)start
    {
        CFNetServiceClientContext   context;
    
        memset(&context, 0, sizeof(context));
        context.info = (__bridge void *)(self);
    
        browser = CFNetServiceBrowserCreate(kCFAllocatorDefault, browserCallBack, &context);
    
        CFNetServiceBrowserScheduleWithRunLoop(browser, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    
        Boolean       status;
        CFStreamError error;
    
        status = CFNetServiceBrowserSearchForServices(
                     browser,
                     (__bridge CFStringRef)self.domain,
                     (__bridge CFStringRef)self.type,
                     &error);
        if (status == 0)
        {
            NSLog(@"error.error == %d\n", (int)error.error);
            [self stopBrowser];
        }
    }

    static void resolveCallBack(
                    CFNetServiceRef theService,
                    CFStreamError*  theError,
                    void*           theInfo)
    {
        NSLog(@"resolveCallback");

        if (theError->error == 0)
        {
            [(__bridge FindServices*)theInfo resolved:theService];
        }
        else
        {
            [(__bridge FindServices*)theInfo resolveFailed:theService withError:theError];
        }
    }
    
    - (void)serviceFound:(CFNetServiceRef)theService
    {
        [self log:@"service found: %@.%@%@" service:theService];
    
        CFNetServiceClientContext   context;
    
        memset(&context, 0, sizeof(context));
        context.info = (__bridge void *)(self);
    
        CFNetServiceSetClient(theService, resolveCallBack, &context);
    
        CFNetServiceScheduleWithRunLoop(theService, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
    
        Boolean       status;
        CFStreamError error;
    
        status = CFNetServiceResolveWithTimeout(theService, 0.0, &error);
        if (status == 0)
        {
            NSLog(@"error.error == %d\n", (int)error.error);
    
           [self stopService:theService];
        }
    }
    
    - (void)serviceLost:(CFNetServiceRef)theService
    {
        [self log: @"service lost: %@.%@%@" service:theService];
    
        NSString* name   = [NSString stringWithFormat:
                                @"%@.%@%@",
                                CFNetServiceGetName(
                                    theService),
                                CFNetServiceGetType(
                                    theService),
                                CFNetServiceGetDomain(
                                    theService)];
        Service* service = [self.services objectForKey:name];
    
        if (service != nil)
        {
            [service lost];
            [self.services removeObjectForKey:name];
            [self.delegate findServices:self didLoseService:service];
        }
        else
        {
            [self log: @"service lost but was not found: %@.%@%@" service:theService];
        }
    }
    
    - (void)resolved:(CFNetServiceRef)theService
    {
        [self log:@"service resolved: %@.%@%@" service:theService];
    
        CFArrayRef addresses = CFNetServiceGetAddressing(theService);
    
        if (CFArrayGetCount(addresses) != 0)
        {
            Service* service = [[Service alloc] init:theService];
    
            [self.services setObject:service forKey:service.name];
            [self.delegate findServices:self didFindService:service];
        }
        else
        {
            NSLog(@"service has 0 addresses: lost ?");
        }
    }
    
    - (void)resolveFailed:(CFNetServiceRef)theService withError:(CFStreamError *)theError
    {
        [self log:@"service resolvedFailed: %@.%@%@" service:theService];
        [self stopService:theService];
    }
    
    - (void)stopBrowser
    {
        CFNetServiceBrowserUnscheduleFromRunLoop(browser, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFNetServiceBrowserInvalidate(browser);
        CFNetServiceBrowserStopSearch(browser, NULL);
    }
    
    - (void)stopService:(CFNetServiceRef)theService
    {
        CFNetServiceClientContext   context;
    
        memset(&context, 0, sizeof(context));
    
        CFNetServiceUnscheduleFromRunLoop(theService, CFRunLoopGetMain(), kCFRunLoopDefaultMode);
        CFNetServiceSetClient(theService, NULL, &context);
        CFNetServiceCancel(theService);
    }
    
    - (void)log:(NSString*)theMessage service:(CFNetServiceRef)theService
    {
        NSLog(
            theMessage,
            CFNetServiceGetName(
                theService),
            CFNetServiceGetType(
                theService),
           CFNetServiceGetDomain(
               theService));
    }
    
    @end

7.0 Examples

In each case FindServices is looking for services of type

    "_ipp._tcp."

in the domain

    "local."

In each case the log output is from FindServices and its delegate running on an iPad running iOS 7.0.

7.1 A Single IPPServer

A single instance of the CUPS test server IPPServer with the name ipp_server_1 running on a Mac and then being stopped.

    ...

    2013-11-14 14:07:07.561 XperTakeTwo[665:60b] browserCallBack
    2013-11-14 14:07:07.564 XperTakeTwo[665:60b] service found: ipp_server_1._ipp._tcp.local.
    2013-11-14 14:07:07.593 XperTakeTwo[665:60b] resolveCallback
    2013-11-14 14:07:07.595 XperTakeTwo[665:60b] service resolved: ipp_server_1._ipp._tcp.local.
    2013-11-14 14:07:07.597 XperTakeTwo[665:60b] findServices:didFindService:<Service: 0x14e86990>
    2013-11-14 14:07:13.931 XperTakeTwo[665:60b] browserCallBack
    2013-11-14 14:07:13.933 XperTakeTwo[665:60b] service lost: ipp_server_1._ipp._tcp.local.
    2013-11-14 14:07:13.935 XperTakeTwo[665:60b] findServices:didLoseService::<Service: 0x14e86990>

    ...

7.2 A Single Printer

A printer being turned on and then turned a couple of minutes later.

Note the lines shown in bold for emphasis.

When the printer is turned off the resolveCallback function is being called for a second time but this time the CFNetService has no addresses.

This is the same thing that happened in this case when using the NSNetServiceBrowser/NSNetService API. (see here)

    ...

    2013-11-14 14:07:55.177 XperTakeTwo[671:60b] browserCallBack
    2013-11-14 14:07:55.180 XperTakeTwo[671:60b] service found: Canon MG6200 series._ipp._tcp.local.
    2013-11-14 14:07:55.250 XperTakeTwo[671:60b] resolveCallback
    2013-11-14 14:07:55.252 XperTakeTwo[671:60b] service resolved: Canon MG6200 series._ipp._tcp.local.
    2013-11-14 14:07:55.255 XperTakeTwo[671:60b] findServices:didFindService:<Service: 0x1452e060>

    2013-11-14 14:09:05.838 XperTakeTwo[671:60b] resolveCallback
    2013-11-14 14:09:05.840 XperTakeTwo[671:60b] service resolved: Canon MG6200 series._ipp._tcp.local.
    2013-11-14 14:09:05.843 XperTakeTwo[671:60b] service has 0 addresses: lost ?

    2013-11-14 14:09:06.910 XperTakeTwo[671:60b] browserCallBack
    2013-11-14 14:09:06.912 XperTakeTwo[671:60b] service lost: Canon MG6200 series._ipp._tcp.local.
    2013-11-14 14:09:06.915 XperTakeTwo[671:60b] findServices:didLoseService::<Service: 0x1452e060>

    ...

Copyright (c) 2013 By Simon Lewis. All Rights Reserved.

Unauthorized use and/or duplication of this material without express and written permission from this blog’s author and owner Simon Lewis is strictly prohibited.

Excerpts and links may be used, provided that full and clear credit is given to Simon Lewis and justanapplication.wordpress.com with appropriate and specific direction to the original content.

Blog at WordPress.com.

%d bloggers like this: