iosobjective-cmacosnetworkextension

What happens if the `NEPacketTunnelflow` method `readPacketsWithCompletionHandler` is called multiple times?


When calling the method

- (void)readPacketsWithCompletionHandler:(void (^)(
    NSArray<NSData *> *packets, NSArray<NSNumber *> *protocols))completionHandler;

the completionHandler is either called directly, in case packets are available at call time, or it is called at a later tim when packets become available.

Yet what is nowhere documented is: What happens if I call this method again before the prior set completionHandler has ever been called?

Will the new handler replace the prior set one and the prior set one won't get called at all anymore?

Are both handler scheduled and called as data arrives? And if so, will they be called in the order I passed them, in reverse order, or in random order?

Has anyone any insights on how that method is implemented?

Of course, I can make a demo project, create a test setup, and see what results I get through testing but that is very time consuming and not necessarily reliable. The problem with unspecified behavior is that it may change at will without letting anyone know. This method may behave differently on macOS and iOS, it may behave differently with every new OS release, or depending on the day of the week.

Or does the fact that nothing is documented is by intention? Do I have to interpret that as: You may call this method once and after your callback was executed, you may call it again with the same or a new callback. Everything else is undefined behavior and you cannot and should not rely on any specific behavior if use that API in a different manner.


Solution

  • As nobody has replied so far, I tried my best to figure it out myself. As testing is not good enough for me, here is what I did:

    First I extracted the NetworkExtension framework binary from the dyld cache of macOS Big Sur using this utility.

    Then I ran otool -Vt over the resulting binary file to get a disassembler dump of the binary.

    My assembly skills are a bit rusty but from what I see the completionHandler is stored in a property named packetHandler, replacing any previous stored value there. Also a callback is created in that method and stored on an object obtained by calling the method interface.

    When looking at the code of this created callback, it obtains the value of the packetHandler property and sets it to NULL after the value was obtained. Then it creates NSData and NSNumber objects, adds those to NSArray objects and calls the obtained handler with those arrays.

    So it seems that calling the method again just replaces the previous completionHandler which is never be called in that case. So you must not rely that a scheduled handler will eventually be called at some time in the future if the tunnel is not teared down if the possibility exists that your code might replace it. Also calling the method multiple times to schedule multiple callbacks has no effect as as only the last one will be kept and eventually be called.