Testing XPC Code With an Anonymous Listener using Low Level C APIs

I have implemented a XPC server using C APIs. I want to write unit tests for it. I came across the following links that use Swift APIs-

  1. Testing and Debugging XPC Code With an Anonymous Listener
  2. TN3113

I have tried to write anonymous listener code and the client code in the same file, using C APIs-

#include <unistd.h>
#include <syslog.h>
#include <pthread.h>
#include <stdio.h>
#include <xpc/xpc.h>
#include <xpc/connection.h>
#include <CoreFoundation/CoreFoundation.h>

static void Anon_Client_Connection_Handler(xpc_connection_t connection, xpc_object_t clientMessage)
{
    const char *description = xpc_copy_description(clientMessage);
    printf("Event received - %s\n", description);
    free((void *)description);
    xpc_type_t type = xpc_get_type(clientMessage);
    if (type == XPC_TYPE_ERROR)
    {
        if (clientMessage == XPC_ERROR_CONNECTION_INVALID)
            printf("Client_Connection_Handler received invalid connection n");
        else if (clientMessage == XPC_ERROR_TERMINATION_IMMINENT)
            printf("Client_Connection_Handler received termination notice n");
    }
    else
    {
        const char *clientMsg = xpc_dictionary_get_string(clientMessage, "message");
        printf("Received from client: %s ", clientMsg);
    }
}

static void Anon_Listener_Connection_Handler(xpc_connection_t connection)
{
    printf("Anon_Listener_Connection_Handler called, setting up event handler \n");
    xpc_connection_set_event_handler(connection, ^(xpc_object_t clientMessage) {
      printf("Processing the connection! \n");
      Anon_Client_Connection_Handler(connection, clientMessage);
    });
    xpc_connection_resume(connection);
}

int main(int argc, const char *argv[])
{
    xpc_connection_t anon_listener = xpc_connection_create(NULL, NULL);
    xpc_connection_set_event_handler(anon_listener, ^(xpc_object_t clientConnection) {
        printf("Client tried to connect \n");
        Anon_Listener_Connection_Handler(clientConnection);
    });

    xpc_connection_resume(anon_listener);
    printf("\nINFO Anonymous connection resumed");
    
    xpc_object_t anon_endpoint = xpc_endpoint_create(anon_listener);

    xpc_connection_t clientConnection = xpc_connection_create_from_endpoint(anon_endpoint);

    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
    xpc_dictionary_set_string(message, "message", "client's message");

    

    xpc_connection_send_message_with_reply(clientConnection, message, dispatch_get_main_queue(), ^(xpc_object_t event) {
        printf("\nINFO inside reply");
        const char *description = xpc_copy_description(event);
        printf("\nINFO %s",description);
        free((void *)description);
    });
    
    xpc_release(message);

    
    xpc_release(anon_listener);
    printf("\nINFO Releasing listener");

    xpc_release(anon_endpoint);
    printf("\nINFO Releasing endpoint");
    // dispatch_main();
    return 0;
}

and this is the output I get

INFO Anonymous connection resumed
INFO Releasing listener
INFO Releasing endpoint

I am not able to connect to the client and exchange messages. Where am I going wrong?

Answered by DTS Engineer in 827634022

Ah, the XPC C API. That’s always fun (-:

The code you posted has a number of problems. I’m gonna start with the highlights. That should get you to the point where the listener receives a connection, which should be sufficient for you to continue making progress by yourself.

So:

  • It’s best to set a target queue on all your connections. It’s not required, but it’ll help keep things straight in your head. I generally use the main queue for these sorts of bring-up tests.

  • You haven’t configured the client connection (clientConnection) at all. You need to call xpc_connection_set_event_handler and xpc_connection_resume on it. I also recommend you call xpc_connection_set_target_queue, per the previous point.

  • You commented out your call to dispatch_main. You’ll need that, otherwise you introduce a race between the main thread returning, which terminates your process, and your XPC messages being handled.

  • You release anon_listener before you that dispatch_main call. I’m not exactly sure what that’ll do, but it doesn’t make sense logically. You want your anonymous listener to be retained while the listener is running.

  • Some of your printf calls are missing the trailing the \n. That makes everything confusing, especially when you take line buffering into account. For bringing up stuff like this, use fprintf and target stderr, which is always unbuffered.

With those changes I was able to get it to the point where Anon_Listener_Connection_Handler runs, and now it’s over to you.

Good luck!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Accepted Answer

Ah, the XPC C API. That’s always fun (-:

The code you posted has a number of problems. I’m gonna start with the highlights. That should get you to the point where the listener receives a connection, which should be sufficient for you to continue making progress by yourself.

So:

  • It’s best to set a target queue on all your connections. It’s not required, but it’ll help keep things straight in your head. I generally use the main queue for these sorts of bring-up tests.

  • You haven’t configured the client connection (clientConnection) at all. You need to call xpc_connection_set_event_handler and xpc_connection_resume on it. I also recommend you call xpc_connection_set_target_queue, per the previous point.

  • You commented out your call to dispatch_main. You’ll need that, otherwise you introduce a race between the main thread returning, which terminates your process, and your XPC messages being handled.

  • You release anon_listener before you that dispatch_main call. I’m not exactly sure what that’ll do, but it doesn’t make sense logically. You want your anonymous listener to be retained while the listener is running.

  • Some of your printf calls are missing the trailing the \n. That makes everything confusing, especially when you take line buffering into account. For bringing up stuff like this, use fprintf and target stderr, which is always unbuffered.

With those changes I was able to get it to the point where Anon_Listener_Connection_Handler runs, and now it’s over to you.

Good luck!

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Testing XPC Code With an Anonymous Listener using Low Level C APIs
 
 
Q