Failure attempting to implement an NSPort subclass for Distributed Objects
Failure attempting to implement an NSPort subclass for Distributed Objects
- Subject: Failure attempting to implement an NSPort subclass for Distributed Objects
- From: Wade Tregaskis <email@hidden>
- Date: Tue, 7 Oct 2003 19:00:25 +1000
Just a warning, this email is very long. I understand that most people
don't particularly care for this topic, so I ask them to have patience
and consideration, and ignore it if so. There's no attachments, I've
tried to be concise, and it's taken me four hours to write, so please
don't abuse me about it.
I'll start from the beginning. I'm implementing a subclass of NSPort
which uses SecureTransport and the 3rd party NetSocket for TCP
communication, i.e. Secure Distributed Objects. The current source is
available from <
http://www.sourceforge.net/projects/securedo/>, under
the BSD license. It doesn't work at the moment, of course, which is
why I'm writing this.
[I subclassed NSPort rather than NSSocketPort because it seemed about
as much effort either way, and I felt that the less I had to try and
bend to NSSocketPort's own socket implementation, the better. In
hindsight this seems like a reasonable decision.]
In short, the problem I'm having is that after sending a message
through and dispatching it, the reply the sent back is not the root
object [or whatever else] requested, but apparently some sort of error.
Since this is all undocumented, I'm not sure what the error (if that's
what it is) is. I'll get the details a little later.
First, I'll describe the whole shebang. I've skipped over some of the
less significant (in my eyes) details, so you might be best off looking
at the code before asking me about anything which is obviously missing
from my description.
I create two of my custom NSPort subclasses, one a listener on a known
port, the other to connect to localhost on that port. The listener
gets given to a server NSConnection as both the send and receive port,
in another thread. The client port gets given to a client NSConnection
as the send port, in the current thread. The receive port given as
nil. This is documented as having the NSConnection automatically
allocated a receive port as necessary, which it does (more on that
later).
Then, I ask the client NSConnection for the "rootProxy". This does
whatever it does inside the NSConnection, getting back into my world
when the NSConnection [or one of it's servants] calls sendBeforeDate:
on the client port I gave it. I then establish a connection and an SSL
session. Note that on the server side this creates a new instance of
my custom NSPort subclass, leaving the original server NSPort subclass
still listening.
All the connection and SSL stuff is done asynchronously (as NetSocket
works asynchronously), so sendBeforeDate: runs a few choice runloops
until the whole process is finished. I imagine this will create
potential deadlocks if you're trying to communicate between two port's
in the same thread, but since that's not a useful scenario anyway, I'm
content to ignore it for the time being.
Once that's done, the code in sendBeforeDate transmits the required
data using SSLWrite(). The protocol it uses is:
// 5-byte header:
//
// [4] Packet length (including this 5-byte header)
// [1] Compression flag (\001 = ON, \000 = OFF)
// Packet structure (not including 5-byte header):
//
// [4] Message ID
// [*] Receiver (if any; may be nil)
// [2] Address length (including this field)
// [*] Address // [4] Object count
// [4] Number of objects
// [*] Objects:
// [4] Object length (including this field and the object type
field)
// [1] Object type (\000 = invalid, \001 = NSData, \002 =
SecureSocketPort)
// [*] Object data
If compression is enabled, the payload (everything except the 5-byte
header) is compressed using zlib. I haven't tested this much, but I
always have it disabled in my testing anyway. There are no data
corruption issues, as I'll prove later.
The "Message ID" is the unsigned msgid parameter passed into the
sendBeforeDate method. Although I do respect it's value and encode it
properly, it is always 0 in my testing, so there's no problems there.
The "Receiver" sub-section encodes the local address of the "from"
parameter passed into sendBeforeDate. This "from" parameter in the
example case is an instance of my subclass created automatically by the
client NSConnection (as I mentioned earlier). This client listener
binds itself to a random free port (49747 in this particular example).
Thus, in my particular case, the encoded form is simply
"131.172.83.106:49747" (where 131.172.83.106 is my IP). Anyway, this
is all encoded fine.
The "Number of Objects" is of course just the number of items in the
components array that was passed in, which is apparently always 1 for
requesting a root object. The single NSData instance that's always
given is encoded as the spec [above] indicates. The data for this
NSData instance is:
...........NSInvocation......NSDistantObject...........rootObject....@@
:..@...D
Or, in hex:
04edfe1f 0e010101 01010d4e 53496e76 6f636174 696f6e00 00010101
104e5344 69737461 6e744f62 6a656374 00000001 01010102 01010b72
6f6f744f 626a6563 74000101 0440403a 00014001 0000
I should note that every retry (caused by repetitive calls to
"rootProxy" on the client NSConnection) sends exactly the same data,
except with a single byte [somewhere at the start] incremented by one
each time - presumably some kind of internal message count or
sequencing number.
Anyway, this is exactly what you see being sent with an NSSocketPort
(using tcpdump), so clearly I'm getting given the same data as an
NSSocketPort, and I'm transmitting it all as expected.
At the server end, the server port (not the one I gave to the server
NSConnection, but the one spawned to handle the received connection)
receives the message and decodes it successfully. I have verified
through multiple approaches that the data is received and is not
changed by a single bit.
When the server port comes to the "Receive" portion of the packet, it
looks for an existing connection to that address and port. In the
first case, one doesn't exist, so it creates a new instance of my
custom port to connect back to the client. This new port is passed on
to the server NSConnection, as I'll later explain.
I reconstruct the components array as an NSMutableArray with the single
NSData in it. The reconstructed data matches the original data
perfectly, so there's no encoding or translation issues (not that I
imagine there would be).
I've then tried two variations of two methods for passing the message
on to the server NSConnection. In the first instance, I create an
NSPortMessage with the given msgid and components, and the send port
set to the new port I created [to connect back to the client], and the
receive port set to 'self'. I then call the server NSConnection's
"handlePortMessage:" with the NSPortMessage.
Alternatively I just create an NSPortCoder from the data, in the same
manner as an NSPortMessage, and call it's dispatch method. That seems
to get the message to the server NSConnection just fine, even though I
don't tell it which NSConnection to go to. Presumably it uses the
default one for the current thread. This seems to be what NSSocketPort
does, from what I can see using 'Sample'. It seems the alternative way
[described earlier] is more robust, but this is a one-liner.
I've tried swapping the send and receive ports around, by the way, on
the chance that I'm interpreting the documentation wrong. This has no
useful effect but to, as you'd expect, simply swap them around,
resulting in the same port being given the reply, which is not the
desired behaviour. It gives my client port(s) headaches anyway,
because the send port doesn't have a delegate set (the client
NSConnection never adds it to a run loop), so it dies when it receives
any message.
Moving on, from this point on my "Sample"ing has shown that the
NSConnection accepts the message just as if it were from a [working]
NSSocketPort, calling it's internal methods until it finally calls
sendBeforeDate: on the server's port to the client's receive port - the
one I created just a few moments ago when discovering the need for it.
Anyway, the "from" port passed in is the server port that dispatched
the message to start with. That seems consistent, and indicates things
are working properly, so far as I can tell. Anyway, this fresh port
connects back to the client at this point, causing the client to spawn
yet another instance of my custom subclass, and doing all it's SSL
stuff as you'd expect.
Back to the server part - as always for these simple messages, the
components array contains just one item, and NSData instance, which has
the following data (in hex):
04cefe2f 0e010100 0000
The 7th byte of that (01h, above) matches the count/sequence byte of
the request, in each message. That's about all I can decipher out of
that message; i.e. that it's not just random gibberish. The last three
bytes, all 00h in my case, are normally all 01h's in the case of the
reply given to an NSSocketPort. That's followed by the actual reply
itself, which as you can see is depressingly absent from my version.
Anyway, this goes through the whole process I've previously described
for sending and receiving the message, winding up with the client
NSConnection getting the appropriate message dispatched to it. This
then results in "nil" being returned for the original call to
"rootProxy". In my sample code (based on the SimpleThreads sample from
Apple), this just loops a thousand times. The whole process is
virtually identical, except that the existing ports are reused; no more
are created from here on in.
So, as you can see, I've got quite a conceptual nightmare on my hands.
I wish I knew why the server NSConnection is balking at the message I
give it, whereas it's quite happy receiving exactly the same message
from an NSSocketPort.
My only suspicion at present is that the server NSConnection receives
the message from a port it does recognise, since it comes from the
spawned server port, not the listening server port that was originally
given to it. In this case it seems to be sending a generic empty reply
back on the send port provided to it. But then the client NSConnection
seems to be accepting that, even though it too receives it from a
spawned port from what is it's actual listener port...
So I'm not sure if that theory is entirely consistent. It does make
some degree of sense. However, I'm understandably hesitant to rewrite
large sections of my code on the off chance that this is the problem.
In particular, it's my experience that these disastrous problems are
more often the result of two parameters being accidentally swapped
around, or something similar, so I'm trying hard to look for such
"newbie" mistakes. I've experimented long and hard, through trial and
error, with pretty much any and all combinations of swappable parts,
without any success. Perhaps it is a glaring design error after all.
From my long experience with this problem it seems pretty clear that
I'm the first and only person to get even this far with a custom
NSPort. Thus, I doubt there's anyone out there in userland who can
help me with this, save to point out any glaring errors. Thus, I'm
hoping [as always] that someone at Apple will smile down upon me. I
don't really wish to drag anyone in by name, but I do understand that
Mr. Doug Davidson is on this list, and has some experience with
Distributed Objects. If you're there Mr. Davidson, I really would
appreciate your help. <:)
Wade Tregaskis
-- Sed quis custodiet ipsos custodes?
P.S. This whole thing may be more appropriate as a DTS incident, given
how very specific it's nature is, but I'm a poor student without any
income, and $200US is just not going to fall out of a tree on me.
Amazingly, some very kind people on the cocoa dev list have responded
to my request to 'borrow' a DTS incident, so to speak, but I feel very
bad using such an item at their expense. Thus, yet another "last hope
post" to try and resolve the issue cheaply. :)
_______________________________________________
macnetworkprog mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/macnetworkprog
Do not post admin requests to the list. They will be ignored.