This document describes how a Cobalt client connects to an already existing Cobalt island somewhere on the network
To connect to an island, a client needs information about how to find
that island. This information is contained in an instance of TPostcard.
TPostcards are typically created out of XML joinme strings or data
returned from an LDAP server.
Connecting consists of the following steps:
0. Have a TPostcard on hand
1. Resolve the TPostcard into a TContact
2. Connect to a Cobalt dispatcher running at some address and port
(given by the TContact instance)
3. Connect to a specific router running on the dispatcher
4. Connect to a specific island running on the router.
0. Have a TPostcard on hand
A client begins connecting by calling the method CroquetHarness >>
openConnectionTo: with its TPostcard instance.
1. Resolve the TPostcard into a TContect
A TPostcard is a high-level description of where to find a router, but a
TContact actually specifies where on the network it may be. The harness
resolves the TPostcard into a TContact by calling TPathNameResolver >>
rosolveContactByPostcard:ifAbsent: (from within CroquetHarness >>
openConnectionTo:ifUnavailable:). I won't go into how that works, as
it's outside the scope of this document.
2. Connect to a Cobalt dispatcher
Once it has a TContact to work with, the harness creates a
CobaltController to manage the high-level connection with the island
[TContact >> setupContactHarness, CobaltHarness >>
connectTo:port:sessionID]. The controller, in turn allocates a
CobaltControllerConnection to handle the low-level communication with
the router. The first step is simple: the connection opens a TCP socket
to the IP address and port specified in the TContact [TMessageRelay >>
connect].
3. Connect to a Cobalt message router
A dispatcher does nothing but connect clients to routers, and also may
create routers, depending on configuration. To connect to a router, the
client sends a 128-bit session
identifier on the newly-opened TCP connection with the dispatcher [TMessageRelay >>
connectTo:port:sessionID:]. If the router is unknown to the dispatcher,
it may create one with that ID, depending on configuration [TSessionDispatcher >>
dispatchConnection:sessionID:]. The router then allocates a
CobaltMessageRouterClient to handle the low-level communication with the client
[TMessageRouter >> acceptConnectionFrom:].
=== Router protocol ===
The client is now connected to the router thru a TCP socket. An instance
of CobaltControllerConnection handles the protocol details on the client
end, and an instance of CobaltMessageRouterClient handles the details on
the router side. From this point forward, the protocol is symmetric;
CobaltControllerConnection and CobaltMessageRouterClient share a common
subclass: TMessageRelay.
The TMessageRelay protocol is an ordered datagram protocol. The client
and router exchange datagrams one after the other, with nothing in
between, over the TCP socket the client and dispatcher established.
The structure of the datagram is this [TMessageRelay >>
runReaderProcess, TMessageRelay >> sendDatagram:]:
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | Size (32-bit big-endian unsigned integer) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 | CRC Checksum (32-bit big-endian unsigned integer) |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
8 | |
+ +
16 | |
+ Facet ID (128-bit UUID) +
24 | |
+ +
32 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 | |
. .
. Payload (<Size> bytes) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The Facet ID and Payload may be encrypted by an ARC4 cipher. The Size
and Checksum never are
Size: the size of the payload, in bytes.
Checksum: CRC checksum of the (decrypted) Facet ID and Payload fields.
Facet ID: an identifier for a payload interpreter
Payload: Uninterpreted raw data of <Size> bytes.
A facet is a method that accepts two arguments: [TMessageRelay >>
invokeFacet:with:]
1. The payload from the datagram <ByteArray>
2. The relay it was delivered to <TMessageRelay>
message format. If <Facet ID> identifies a facet the
receiver understands, the facet will interpret the payload
Each facet has a name and an identifier. The name is a simple string, and the
identifier is a 128-bit UUID.
A Cobalt router understands the following facets:
Reciever (Router) Reciever Name (Router) ID
Sender (Client) Sender Name (Client) Creator
------------------------------------------------------------------------------------------------------------------------------------------------
CobaltMessageRouter >> requestRouterInfo:from: CobaltController class >> requestAuthServicesFacet a554dfd6b22eb0708bbf938da499be82
CobaltController >> requestRouterInfo CobaltController class >> requestAuthServicesFacet CobaltMessageRouter >> initializeFacets
CobaltMessageRouter >> login:from: CobaltAuthService class >> loginFacet bbd9dab1b2cb31cf9837d3b463cfe931
CobaltAuthService >> login:password:controller: CobaltAuthService class >> loginFacet CobaltMessageRouter >> initializeWithConfig:
CobaltMessageRouter >> identify:from: CobaltMessageRouterClient ivar identifyFacet <random per connection>
CoPublicKeyAuthService >> challenge:from:controller: temporary identifyFacet CoPublicKeyAuthService >> login:from:router:
CobaltMessageRouter >> listFacets:from: TMessageRouterClient ivar listFacet <random per connection>
CobaltAuthService >> challenge:from:controller: temporary listFacet CobaltAuthService >> login:from:router:
TMessageRouter >> join:from: facet joinFacet <random per router>
TRemoteController >> join facet join TMessageRouter >> initializeFacets
TMessageRouter >> leave:from: facet leaveFacet <random per router>
TRemoteController >> leave facet leave TMessageRouter >> initializeFacets
TMessageRouter >> sync:from: facet syncFacet <random per router>
TRemoteController >> sync: facet sync TMessageRouter >> initializeFacets
TMessageRouterClient >> syncReply:from: <none> <random per request>
TRemoteController >> serve:from: temporary facet TMessageRouter >> sync:from:
TMessageRouter >> heartbeat:from: facet heartbeatFacet <random per router>
TRemoteController >> heartbeat: facet heartbeat TMessageRouter >> initializeFacets
TMessageRouter >> beServer:from: facet beServerFacet <random per router>
TRemoteController >> beServer facet beServer TMessageRouter >> initializeFacets
TMessageRouter >> send:from: facet sendFacet <random per router>
TRemoteController >> sendMessage: facet send TMessageRouter >> initializeFacets
TMessageRouter >> timeStamp:from: facet timeStampFacet <random per router>
TRemoteController >> routerStamp: facet timeStamp TMessageRouter >> initializeFacets
------------------------------------------------------------------------------------------------------------------------------------------------
A Cobalt client understands the following facets:
Reciever (Client) Reciever Name (Client) ID
Sender (Router) Sender Name (Router) Creator
------------------------------------------------------------------------------------------------------------------------------------------------
CobaltController >> routerInfo:from: facet routerInfo <random per request>
CobaltMessageRouter >> requestRouterInfo:from: temporary routerInfoFacet CobaltController >> requestRouterInfo
CobaltController >> challenge:from: facet challenge <random per request>
CobaltAuthService >> login:from:router: temporary challengeFacet CobaltAuthService >> login:password:controller:
CobaltController >> recvFacets:from: facet recvFacets <random per request>
CobaltAuthService >> listFacetsTo:from:router: temporary responseFacet CobaltAuthService >> challenge:from:controller:
TRemoteController >> joinComplete:from: facet joinComplete <random per request>
TMessageRouter >> join:from: temporary respFacet TRemoteController >> join
TRemoteController >> sync:from: facet syncFrom <random per request>
TMessageRouter >> syncReply:from: TMessageRouterClient ivar syncFacet TRemoteController >> sync:
TRemoteController >> serve:from: facet serve <random per client>
TMessageRouter >> sync:from: TMessageRouterClient ivar serveFacet TRemoteController >> beServer
TRemoteController >> recv:from: facet recv <random per request>
TMessageRouter >> dispatchMessage: TMessageRouterClient ivar recvFacet TRemoteController >> join
TRemoteController >> tick:from: facet tick <random per request>
TMessageRouterClient >> sendTick TMessageRouterClient ivar tickFacet TRemoteController >> heartbeat:
TRemoteController >> stampComplete:from: facet stampComplete <random per request>
TMessageRouterClient >> sendTick TMessageRouterClient ivar tickFacet TRemoteController >> routerStamp:
------------------------------------------------------------------------------------------------------------------------------------------------
4. Authenticate to the router
All of the client-side steps to connect to an island are in
CobaltHarness >> connectToIslandAt:port:sessionId:
- Router installs core facets for the connection, but doesn't tell
the client about them [...]
Note: I think the router uses the same facet ID's for the core facets of
every connection (join, send, sync, etc...). This is a security hole, as
it could allow a logged-in client to give other clients access to facets
the router hasn't authorized them to use. Check how
CobaltMessageRouterClient is created in the router.
- Client sends router a requestRouterInfo datagram [CobaltController >>
requestRouterInfo]
- Router sends client a routerInfo datagram [CobaltMessageRouter >>
requestRouterInfo:from:]
- This causes the promise p in CobaltHarness >>
connectTo:port:sessionID: to resolve
- Client sends router a login datagram [CobaltController >>
loginWithCredential:]
- If authentication fails, the router closes the TCP socket
[TMessageRouterClient >> destroy]
- Router installs list facet [CobaltAuthService >> login:from:router:]
- Router sends client a challenge datagram [CobaltMessageRouterClient >>
loginData:]
- Router begins sending and only accepting encrypted traffic
[CobaltAuthService >> login:from:router:]
- Client decodes the challenge datagram and installs the encryption keys
[CobaltAuthService >> challenge:from:controller:]
- Client sends router a list datagram (or identify if using public key
authentication). The client has to successfully decode the encryption
keys and the facet ID from the challenge datagram in order to do this.
[CobaltAuthService >> challenge:from:controller:]
- Router authenticates the user if an identity datagram was sent, and
closes the socket if auth failed [CoPublicKeyAuthService >>
identity:from:router:]
- Router collects a list of facets the user is allowed to send to
[CobaltAuthService >> facetsForUser:authServices:]. Current demo
implementations require this to send all facets regardless of user,
namely: join, send, sync, heartbeat, beServer, leave, and timeStamp
[CobaltAuthService >> allFacets, CobaltController >> recvFacets:from:]
- Router packs the facets into a recvFacets datagram and sends it to the
client [CobaltAuthService >> challenge:from:controller:]
- Client installs the facets and resolves the login promise
CobaltHarness >> connectToIslandAt:port:sessionID: is waiting on
[CobaltController >> recvFacets:from:]
5. Connect to the island
We're still in CobaltHarness >> connectToIslandAt:port:sessionID:
- Client sends router a join datagram [CobaltController >> join]
- Router sends client a joinComplete datagram [TMessageRouter >>
join:from:]
- Client begins receiving a stream of update messages from the router
and buffering them [TRemoteController >> recv:from:]
- Client sends router a sync datagram [CobaltController >> sync]
- Router forwards the sync datagram to a server in the form of a serve
datagram [TMessageRouter >> sync:from:]
- Server sends the router a snapshot of the island in a SyncReply
datagram [TRemoteController >> serve:from:]
- Router forwards the sync reply datagram to the client in the form of a
syncFrom datagram [TMessageRouterClient >> syncReply:from:]
- Client leads the snapshot and discards all messages that are older
than the snapshot, and waits for a newer one to arrive
[TRemoteController >> install:]
- The server that provided the snapshot sends a no-op message thru the
router (Object >> yourself), which will be later than the snapshot
[TRemoteController >> serve:from:]
- Client recieves a message later than the snapshot (which is probably,
but not necessarily, the one the server just sent. There may be other
traffic on the island). The client executes the message and is done
installing the snapshot. [TRemoteController >> install:]
- Client sends a heartbeat datagram to the router requesting tick
messages be sent every 20 milliseconds [TRemoteController >>
heartbeat:]
6: tell the harness about the island
- addController: and addIsland:postcard: at the end of CroquetHarness >>
openConnectionTo:ifUnavailable
7: teleport or make a portal to the island
- TPostcard >> makePortalForAvatar:portalType:
- The avatar sends (or at least recieves) 3 messages during
CroquetHarness >> gotoSnapshot:
=== Facet Details ===
requestRouterInfo
Payload
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Reply Facet ID (128-bit UUID) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
requestRouterInfo reply
Revocation: on send
Payload
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
. .
. DataStream encoded reply (<Size> bytes) .
. .
end | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
The DataStream encodes an Array with the following
data [CobaltMessageRouter >> requestRouterInfo:from, CobaltController >>
unpackRouterInfo:]:
objOut <Array>
1. dispatcher dispatcherInfo <Array>
1. dispatcher uuid <UUID>
2. dispatcher internalContactInfo <TContact>
3. dispatcher externalContactInfo <TContact or nil>
2. CobaltMessageRouter >> authServicesInfo <Array>
1. authServices first listForController <Array>
1. authService class name <Symbol>
1. authService serviceName <String>
2. authServices second listForController <Array>
3. etc...
login
Payload
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | Version ID (2 bytes: 0x0100) | n = Service Name Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4 | |
. .
. Authentication Service Name (UTF-8 string of n bytes) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
4+n | |
+ +
8+n | |
+ Response Facet ID (128 bits) +
12+n | |
+ +
16+n | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
20+n | |
. .
. Authentication Payload (<Size>-n-20 bytes) .
. .
end | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
length: 2-byte big-endian unsigned integer < 1024
Service name must be
LocalSecret Auth Payload
[CobaltLocalSecretTestAuthService >> login:pass:controller:,
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
. .
. Username (UTF-8 encoded string of <Size> bytes) .
. .
end | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Challenge (Router -> Client)
Revocation: on receive
Payload is ARC4 encrypted with the user's password or public key
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Send Session Key (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ +
20 | |
+ Receive Session Key (128 bits) +
24 | |
+ +
28 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 | |
+ +
36 | |
+ Next Facet ID (128-bit UUID) +
40 | |
+ +
44 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Next facet is either list (for most authentication services), or identify
(for public key authentication)
List (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Receive Facets Facet ID (128-bit UUID) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Identify (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Receive Facets Facet ID (128-bit UUID) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | n = Username Length | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
20 | |
. .
. Username (UTF-8 string of n bytes) .
. .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
18+n | m = Password Length | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ +
22+n | |
. .
. Password (UTF-8 string of m bytes) .
. .
end | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
m, n < 1024 [String >> asLVRouterData]
recvFacets (Router -> Client)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Join Facet ID (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ +
20 | |
+ Send Facet ID (128 bits) +
24 | |
+ +
28 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
32 | |
+ +
36 | |
+ Sync Facet ID (128 bits) +
40 | |
+ +
44 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
48 | |
+ +
52 | |
+ Heartbeat Facet ID (128 bits) +
56 | |
+ +
60 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
64 | |
+ +
68 | |
+ BeServer Facet ID (128 bits) +
72 | |
+ +
76 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
80 | |
+ +
84 | |
+ Leave Facet ID (128 bits) +
88 | |
+ +
92 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
96 | |
+ +
100 | |
+ TimeStamp Facet ID (128 bits) +
104 | |
+ +
108 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Join (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Recv Facet ID (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ +
20 | |
+ Response Facet ID (128 bits) +
24 | |
+ +
28 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
JoinComplete (router -> client)
no payload
Sync (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Response Facet ID (on client) (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ +
20 | Session ID (for sync requests) (128 bits) |
+ - or - +
24 | Resource ID (for resource requests) (128 bits) |
+ +
28 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Serve (Router -> Server)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Response Facet ID (on router) (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ +
20 | Session ID (for sync requests) (128 bits) |
+ - or - +
24 | Resource ID (for resource requests) (128 bits) |
+ +
28 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SyncReply (Server -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
. TSnapshotWriter-encoded island snapshot (for sync requests) .
. - or - .
. ??? (for resource requests) .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
SyncFrom (Router -> Client)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
. TSnapshotWriter-encoded island snapshot (for sync requests) .
. - or - .
. ??? (for resource requests) .
| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Heartbeat (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Response Facet ID (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | tickPeriod (milliseconds) (32-bit big-endian unsigned integer)|
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Tick (Router -> Client)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ timeStamp (IEEE 754 double-precision floating point number) +
4 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Leave (Client -> Router)
no payload
also causes the router to close the tcp connection
timeStamp (Client -> Router)
bits 0 8 16 24 31
bytes 0 1 2 3
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
0 | |
+ +
4 | |
+ Response Facet ID (128 bits) +
8 | |
+ +
12 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
16 | |
+ timeStamp (IEEE 754 double-precision floating point number) +
20 | |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
stampComplete (Router -> Client)
no payload