diff --git a/build.xml b/build.xml
new file mode 100644
index 000000000..02cb9c71b
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,54 @@
+
+clientWillDisconnect on all of the client observers
+ * and abort the logoff process if any of them return false. If false,
+ * clientWillDisconnect will not be called at all.
+ *
+ * @return true if the logoff succeeded, false if it failed due to a
+ * disagreeable observer.
+ */
+ public boolean logoff (boolean abortable)
+ {
+ // if the request is abortable, let's run it past the observers
+ // before we act upon it
+ if (abortable && notifyObservers(CLIENT_WILL_LOGOFF, null)) {
+ return false;
+ }
+
+ // ask the communicator to send a logoff message and disconnect
+ // from the server
+ _comm.logoff();
+
+ return true;
+ }
+
+ boolean notifyObservers (int code, Exception cause)
+ {
+ boolean rejected = false;
+ synchronized (_observers) {
+ for (int i = 0; i < _observers.size(); i++) {
+ ClientObserver obs = (ClientObserver)_observers.get(i);
+ switch (code) {
+ case CLIENT_DID_LOGON:
+ obs.clientDidLogon(this);
+ break;
+ case CLIENT_FAILED_TO_LOGON:
+ obs.clientFailedToLogon(this, cause);
+ break;
+ case CLIENT_CONNETION_FAILED:
+ obs.clientConnectionFailed(this, cause);
+ break;
+ case CLIENT_WILL_LOGOFF:
+ if (!obs.clientWillLogoff(this)) {
+ rejected = true;
+ }
+ break;
+ case CLIENT_DID_LOGOFF:
+ obs.clientDidLogoff(this);
+ break;
+ default:
+ throw new RuntimeException("Invalid code supplied to " +
+ "notifyObservers: " + code);
+ }
+ }
+ }
+ return rejected;
+ }
+
+ synchronized void communicatorDidExit ()
+ {
+ // clear out our communicator reference
+ _comm = null;
+ }
+
+ protected Credentials _creds;
+ protected String _hostname;
+ protected int _port;
+
+ /** Our list of client observers. */
+ protected List _observers = new ArrayList();
+
+ /** The entity that manages our network communications. */
+ protected Communicator _comm;
+
+ // client observer codes
+ static final int CLIENT_DID_LOGON = 0;
+ static final int CLIENT_FAILED_TO_LOGON = 1;
+ static final int CLIENT_CONNETION_FAILED = 2;
+ static final int CLIENT_WILL_LOGOFF = 3;
+ static final int CLIENT_DID_LOGOFF = 4;
+}
diff --git a/src/java/com/threerings/presents/client/ClientObserver.java b/src/java/com/threerings/presents/client/ClientObserver.java
new file mode 100644
index 000000000..7289a16ad
--- /dev/null
+++ b/src/java/com/threerings/presents/client/ClientObserver.java
@@ -0,0 +1,72 @@
+//
+// $Id: ClientObserver.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.client;
+
+/**
+ * A client observer is registered with the client instance to be notified
+ * when state changes happen within the client. Specifically, logon
+ * sucess/failure and logoff.
+ *
+ *
These callbacks happen on the client thread and should therefore + * not be used to perform any complex action or do much more than relay + * the signal to some other thread (like the AWT thread) to act more fully + * on the notice. + * + *
In the normal course of affairs, clientDidLogon will
+ * be called after the client successfully logs on to the server and
+ * clientDidLogoff will be called after the client logs off
+ * of the server. If logon fails for any reson,
+ * clientFailedToLogon will be called to explain the failure.
+ *
+ *
clientWillLogoff will only be called when an abortable
+ * logoff is requested (like when the user clicks on a logoff button of
+ * some sort). It will not be called during non-abortable logoff requests
+ * (like when the browser calls stop on the applet and is about to yank
+ * the rug out from under us). If an observer aborts the logoff request,
+ * it should notify the user in some way why the request was aborted
+ * (but it shouldn't do so on the thread that calls
+ * clientWillLogoff).
+ *
+ *
If the client connection fails unexpectedly,
+ * clientConnectionFailed will be called to let the
+ * observers know that we lost our connection to the
+ * server. clientDidLogoff will be called immediately
+ * afterwards as a normal logoff procedure is effected.
+ */
+public interface ClientObserver
+{
+ /**
+ * Called after the client successfully connected to and authenticated
+ * with the server. The entire object system is up and running by the
+ * time this method is called.
+ */
+ public void clientDidLogon (Client client);
+
+ /**
+ * Called if anything fails during the logon attempt. This could be a
+ * network failure, authentication failure or otherwise. The exception
+ * provided will indicate the cause of the failure.
+ */
+ public void clientFailedToLogon (Client client, Exception cause);
+
+ /**
+ * Called when the connection to the server went away for some
+ * unexpected reason. This will be followed by a call to
+ * clientDidLogoff.
+ */
+ public void clientConnectionFailed (Client client, Exception cause);
+
+ /**
+ * Called when an abortable logoff requrest is made. If the observer
+ * returns false from this method, the client will abort the logoff
+ * request.
+ */
+ public boolean clientWillLogoff (Client client);
+
+ /**
+ * Called after the client has been logged off of the server and has
+ * disconnected.
+ */
+ public void clientDidLogoff (Client client);
+}
diff --git a/src/java/com/threerings/presents/client/Communicator.java b/src/java/com/threerings/presents/client/Communicator.java
new file mode 100644
index 000000000..911017622
--- /dev/null
+++ b/src/java/com/threerings/presents/client/Communicator.java
@@ -0,0 +1,175 @@
+//
+// $Id: Communicator.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.client;
+
+import java.io.*;
+import java.net.Socket;
+import java.net.InetAddress;
+
+import com.samskivert.util.Queue;
+import com.samskivert.cocktail.cher.Log;
+import com.samskivert.cocktail.cher.net.*;
+
+/**
+ * The client performs all network I/O on separate threads (one for
+ * reading and one for writing). The communicator class encapsulates that
+ * functionality.
+ *
+ *
+ * Logon synopsis:
+ *
+ * Client.logon():
+ * - Calls Communicator.start()
+ * Communicator.start():
+ * - spawn Reader thread
+ * Reader.run():
+ * { - connect
+ * - authenticate
+ * } if either fail, notify observers of failed logon
+ * - start writer thread
+ * - notify observers that we're logged on
+ * - read loop
+ * Writer.run():
+ * - write loop
+ *
+ */
+class Communicator
+{
+ /**
+ * Creates a new communicator instance which is associated with the
+ * supplied client.
+ */
+ public Communicator (Client client)
+ {
+ _client = client;
+ }
+
+ /**
+ * Logs on to the server and initiates our full-duplex message
+ * exchange.
+ */
+ public void logon ()
+ {
+ // make sure things are copacetic
+ if (_reader != null) {
+ throw new RuntimeException("Communicator already started.");
+ }
+
+ // start up the reader thread. it will connect to the server and
+ // start up the writer thread if everything went successfully
+ _reader = new Reader();
+ _reader.start();
+ }
+
+ /**
+ * Delivers a logoff notification to the server and shuts down the
+ * network connection. Also causes all communication threads to
+ * terminate.
+ */
+ public void logoff ()
+ {
+ }
+
+ /**
+ * Queues up the specified message for delivery to the server.
+ */
+ public void postMessage (UpstreamMessage msg)
+ {
+ // simply append the message to the queue
+ _msgq.append(msg);
+ }
+
+ protected void startWriter ()
+ {
+ if (_writer != null) {
+ throw new RuntimeException("Writer already started!?");
+ }
+
+ // create a new writer thread and start it up
+ _writer = new Writer();
+ _writer.start();
+ }
+
+ protected synchronized void readerDidExit ()
+ {
+ // clear out our reader reference
+ _reader = null;
+
+ // let the client know when we finally go away
+ _client.communicatorDidExit();
+ }
+
+ protected class Reader extends Thread
+ {
+ public void run ()
+ {
+ try {
+ // first we connect and authenticate with the server
+ try {
+ // connect to the server
+ connect();
+
+ // then authenticate
+ logon();
+
+ } catch (Exception e) {
+ // let the observers know that we've failed
+ _client.notifyObservers(Client.CLIENT_FAILED_TO_LOGON, e);
+ // and terminate our communicator thread
+ return;
+ }
+
+ // once authenticated, we go into full-duplex mode,
+ // starting up another thread to listen for messages while
+ // we handle the delivery of messages
+ startWriter();
+ listen();
+
+ } finally {
+ // let the communicator know when we finally go away
+ readerDidExit();
+ }
+ }
+
+ protected void connect ()
+ throws IOException
+ {
+ // if we're already connected, we freak out
+ if (_socket != null) {
+ throw new IOException("Already connected.");
+ }
+
+ // look up the address of the target server
+ InetAddress host = InetAddress.getByName(_client.getHostname());
+
+ // establish a socket connection to said server
+ _socket = new Socket(host, _client.getPort());
+ }
+
+ protected void logon ()
+ throws LogonException
+ {
+ }
+
+ protected void listen ()
+ {
+ }
+ }
+
+ protected class Writer extends Thread
+ {
+ public void run ()
+ {
+ }
+ }
+
+ protected Client _client;
+ protected Reader _reader;
+ protected Writer _writer;
+
+ protected Socket _socket;
+ protected DataInputStream _din;
+ protected DataInputStream _dout;
+ protected Queue _msgq = new Queue();
+}
diff --git a/src/java/com/threerings/presents/client/LogonException.java b/src/java/com/threerings/presents/client/LogonException.java
new file mode 100644
index 000000000..74030412e
--- /dev/null
+++ b/src/java/com/threerings/presents/client/LogonException.java
@@ -0,0 +1,20 @@
+//
+// $Id: LogonException.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.client;
+
+/**
+ * A logon exception is used to indicate a failure to log on to the
+ * server. The reason for failure is encoded as a string and stored in the
+ * message field of the exception.
+ */
+public class LogonException extends Exception
+{
+ /**
+ * Constructs a logon exception with the supplied logon failure code.
+ */
+ public LogonException (String code)
+ {
+ super(code);
+ }
+}
diff --git a/src/java/com/threerings/presents/dobj/Event.java b/src/java/com/threerings/presents/dobj/Event.java
new file mode 100644
index 000000000..572e30ce3
--- /dev/null
+++ b/src/java/com/threerings/presents/dobj/Event.java
@@ -0,0 +1,21 @@
+//
+// $Id: Event.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.dobj;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class Event
+{
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ }
+}
diff --git a/src/java/com/threerings/presents/io/ObjectStreamException.java b/src/java/com/threerings/presents/io/ObjectStreamException.java
new file mode 100644
index 000000000..4627b8bab
--- /dev/null
+++ b/src/java/com/threerings/presents/io/ObjectStreamException.java
@@ -0,0 +1,19 @@
+//
+// $Id: ObjectStreamException.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.io;
+
+/**
+ * The object stream exception is used to communicate an error in
+ * processing a typed object stream. As any error is fatal, we don't
+ * differentiate exceptions with separate classes, but instead communicate
+ * the nature of the error in the exception message with the assumption
+ * that the caller will want to raise holy hell in every case.
+ */
+public class ObjectStreamException extends Exception
+{
+ public ObjectStreamException (String message)
+ {
+ super(message);
+ }
+}
diff --git a/src/java/com/threerings/presents/io/TypedObject.java b/src/java/com/threerings/presents/io/TypedObject.java
new file mode 100644
index 000000000..da873c6d4
--- /dev/null
+++ b/src/java/com/threerings/presents/io/TypedObject.java
@@ -0,0 +1,49 @@
+//
+// $Id: TypedObject.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.io;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+/**
+ * A typed object is one that is associated with a particular type code.
+ * The type code can be communicated on the wire and used by the receiving
+ * end to instantiate the proper typed object class for decoding.
+ */
+public abstract class TypedObject
+{
+ /**
+ * Each typed object class must associate itself with a type value via
+ * the TypedObjectFactory so that the factory can
+ * instantiate the proper TypedObject derived class when
+ * decoding an incoming message.
+ *
+ * @return The type code associated with this object.
+ */
+ public abstract short getType ();
+
+ /**
+ * Each typed object class must be able to write itself to a stream.
+ * It should first call super.writeTo() before writing
+ * its own fields to the stream.
+ */
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ out.writeShort(getType());
+ }
+
+ /**
+ * Each typed object class must be able to read itself from a stream.
+ * It should first call super.readFrom() before reading
+ * its own fields from the stream.
+ */
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ // nothing to do because the TypedObjectFactory already read our
+ // type value from the stream
+ }
+}
diff --git a/src/java/com/threerings/presents/io/TypedObjectFactory.java b/src/java/com/threerings/presents/io/TypedObjectFactory.java
new file mode 100644
index 000000000..97332a271
--- /dev/null
+++ b/src/java/com/threerings/presents/io/TypedObjectFactory.java
@@ -0,0 +1,77 @@
+//
+// $Id: TypedObjectFactory.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.io;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.util.HashMap;
+
+/**
+ * The encodable factory is used to unserialize encodable object instances
+ * from a stream. It maintains a mapping of encodable object classes to
+ * identifier codes which it uses to determine what sort of object is on
+ * the stream. The encodable mechanism is used to associate a particular
+ * class with some compact code that can be transmitted on the wire to
+ * identify that class.
+ */
+public class TypedObjectFactory
+{
+ /**
+ * Reads (unserializes) a typed object from the supplied data input
+ * stream.
+ *
+ * @return The unserialized typed object instance.
+ */
+ public static TypedObject readFrom (DataInputStream din)
+ throws IOException, ObjectStreamException
+ {
+ // first determine the type of the incoming object
+ short type = din.readShort();
+
+ // now instantiate the proper object and decode the remainder
+ TypedObject msg = newObjectByType(type);
+ msg.readFrom(din);
+
+ return msg;
+ }
+
+ protected static TypedObject newObjectByType (short type)
+ throws ObjectStreamException
+ {
+ Class clazz = (Class)_classes.get(new Short(type));
+ if (clazz == null) {
+ String errmsg = "Unknown object type: " + type;
+ throw new ObjectStreamException(errmsg);
+ }
+
+ try {
+ return (TypedObject)clazz.newInstance();
+ } catch (Throwable t) {
+ String errmsg = "Unable to instantiate typed object " +
+ "[class=" + clazz.getName() + ", error=" + t + "].";
+ throw new ObjectStreamException(errmsg);
+ }
+ }
+
+ protected static void registerClass (short type, Class clazz)
+ {
+ Short key = new Short(type);
+
+ // make sure no funny business is afoot
+ if (_classes.containsKey(key)) {
+ Class incumbent = (Class)_classes.get(key);
+ String errmsg = "Cannot register " + clazz.getName() +
+ " as type " + type + " because " + incumbent.getName() +
+ " is already registered with that type.";
+ throw new RuntimeException(errmsg);
+ }
+
+ // set up the mapping
+ _classes.put(key, clazz);
+ }
+
+ /** Our type to class mapping table. */
+ protected static HashMap _classes = new HashMap();
+}
diff --git a/src/java/com/threerings/presents/net/AuthRequest.java b/src/java/com/threerings/presents/net/AuthRequest.java
new file mode 100644
index 000000000..9fc21cf7a
--- /dev/null
+++ b/src/java/com/threerings/presents/net/AuthRequest.java
@@ -0,0 +1,57 @@
+//
+// $Id: AuthRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+import com.samskivert.cocktail.cher.io.TypedObjectFactory;
+
+public class AuthenticationRequest extends UpstreamMessage
+{
+ /** The code for an object subscription request. */
+ public static final short TYPE = TYPE_BASE + 0;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public AuthenticationRequest ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a authentication request with the supplied credentials.
+ */
+ public AuthenticationRequest (Credentials creds)
+ {
+ _creds = creds;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ _creds.writeTo(out);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _creds = (Credentials)TypedObjectFactory.readFrom(in);
+ }
+
+ /**
+ * The object id of the distributed object to which we are
+ * subscribing.
+ */
+ protected Credentials _creds;
+}
diff --git a/src/java/com/threerings/presents/net/Credentials.java b/src/java/com/threerings/presents/net/Credentials.java
new file mode 100644
index 000000000..3dfc6d3fc
--- /dev/null
+++ b/src/java/com/threerings/presents/net/Credentials.java
@@ -0,0 +1,33 @@
+//
+// $Id: Credentials.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import com.samskivert.cocktail.cher.io.TypedObject;
+import com.samskivert.cocktail.cher.io.TypedObjectFactory;
+
+/**
+ * Credentials are supplied by the client implementation and sent along to
+ * the server during the authentication process. To provide support for a
+ * variety of authentication methods, the credentials class is meant to be
+ * subclassed for the particular method (ie. username + password) in use
+ * in a given system.
+ *
+ * All derived classes should provide a no argument constructor so
+ * that they can be instantiated prior to reconstruction from a data input
+ * stream.
+ */
+public abstract class Credentials extends TypedObject
+{
+ /**
+ * All credential derived classes should base their typed object code
+ * on this base value.
+ */
+ public static final short TYPE_BASE = 300;
+
+ // register our credential classes
+ static {
+ TypedObjectFactory.registerClass(UsernamePasswordCreds.TYPE,
+ UsernamePasswordCreds.class);
+ }
+}
diff --git a/src/java/com/threerings/presents/net/DownstreamMessage.java b/src/java/com/threerings/presents/net/DownstreamMessage.java
new file mode 100644
index 000000000..0cffaacb7
--- /dev/null
+++ b/src/java/com/threerings/presents/net/DownstreamMessage.java
@@ -0,0 +1,72 @@
+//
+// $Id: DownstreamMessage.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import com.samskivert.cocktail.cher.io.TypedObject;
+import com.samskivert.cocktail.cher.io.TypedObjectFactory;
+
+/**
+ * The DownstreamMessage class encapsulates a message in the
+ * Distributed Object Protocol that flows from the server to the
+ * client. Downstream messages include object subscription, event
+ * forwarding and session management.
+ */
+public class DownstreamMessage
+{
+ /**
+ * All downstream message derived classes should base their typed
+ * object code on this base value.
+ */
+ public static final short TYPE_BASE = 200;
+
+ /**
+ * The message id of the upstream message with which this downstream
+ * message is associated (or -1 if it is not associated with any
+ * upstream message). Because not every downstream message class cares
+ * to provide an upstream message id, this field is not serialized
+ * when the base downstream message class is serialized. Thus derived
+ * classes that care about message id should take care to initialize,
+ * serialize and unserialize the value theirselves.
+ */
+ public short messageId = -1;
+
+ /**
+ * Each downstream message derived class must provide a zero argument
+ * constructor so that the TypedObjectFactory can create
+ * a new instance of said class prior to unserializing it.
+ */
+ public DownstreamMessage ()
+ {
+ // nothing to do...
+ }
+
+ // register our downstream message classes
+ static {
+ TypedObjectFactory.registerClass(AuthenticationRequest.TYPE,
+ AuthenticationRequest.class);
+ TypedObjectFactory.registerClass(SubscribeRequest.TYPE,
+ SubscribeRequest.class);
+ TypedObjectFactory.registerClass(FetchRequest.TYPE,
+ FetchRequest.class);
+ TypedObjectFactory.registerClass(UnsubscribeNotification.TYPE,
+ UnsubscribeNotification.class);
+ TypedObjectFactory.registerClass(ForwardEventNotification.TYPE,
+ ForwardEventNotification.class);
+ TypedObjectFactory.registerClass(PingNotification.TYPE,
+ PingNotification.class);
+ TypedObjectFactory.registerClass(LogoffNotification.TYPE,
+ LogoffNotification.class);
+ }
+
+ /** The code for an event notification. */
+ public static final byte EVENT = 0x00;
+ /** The code for a fetch/subscribe object response. */
+ public static final byte OBJECT = 0x01;
+ /** The code for a request failure. */
+ public static final byte FAILURE = 0x02;
+ /** The code for a pong response. */
+ public static final byte PONG = 0x03;
+ /** The code for an end of transmission notification. */
+ public static final byte EOT = 0x04;
+}
diff --git a/src/java/com/threerings/presents/net/EventNotification.java b/src/java/com/threerings/presents/net/EventNotification.java
new file mode 100644
index 000000000..52769a633
--- /dev/null
+++ b/src/java/com/threerings/presents/net/EventNotification.java
@@ -0,0 +1,48 @@
+//
+// $Id: EventNotification.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+public class EventNotification extends DownstreamMessage
+{
+ /** The code for an event notification. */
+ public static final short TYPE = TYPE_BASE + 1;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public EventNotification ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs an event notification for the supplied event.
+ */
+ public EventNotification (Event event)
+ {
+ _event = event;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ _event.writeTo(out);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _event = EventFactory.readFrom(in);
+ }
+
+ /** The event which we are forwarding. */
+ protected Event _event;
+}
diff --git a/src/java/com/threerings/presents/net/FetchRequest.java b/src/java/com/threerings/presents/net/FetchRequest.java
new file mode 100644
index 000000000..4d6acb527
--- /dev/null
+++ b/src/java/com/threerings/presents/net/FetchRequest.java
@@ -0,0 +1,55 @@
+//
+// $Id: FetchRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class FetchRequest extends UpstreamMessage
+{
+ /** The code for an object fetch request. */
+ public static final short TYPE = TYPE_BASE + 2;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public FetchRequest ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a fetch request for the distributed object with the
+ * specified object id.
+ */
+ public FetchRequest (int oid)
+ {
+ _oid = oid;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeInt(_oid);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _oid = in.readInt();
+ }
+
+ /**
+ * The object id of the distributed object which we are fetching.
+ */
+ protected int _oid;
+}
diff --git a/src/java/com/threerings/presents/net/ForwardEventRequest.java b/src/java/com/threerings/presents/net/ForwardEventRequest.java
new file mode 100644
index 000000000..290f5f7ff
--- /dev/null
+++ b/src/java/com/threerings/presents/net/ForwardEventRequest.java
@@ -0,0 +1,55 @@
+//
+// $Id: ForwardEventRequest.java,v 1.1 2001/05/22 06:07:59 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+import com.samskivert.cocktail.cher.dobj.Event;
+import com.samskivert.cocktail.cher.dobj.EventFactory;
+
+public class ForwardEventNotification extends UpstreamMessage
+{
+ /** The code for a forward event notification. */
+ public static final short TYPE = TYPE_BASE + 4;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public ForwardEventNotification ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a forward event notification for the supplied event.
+ */
+ public ForwardEventNotification (Event event)
+ {
+ _event = event;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ _event.writeTo(out);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _event = EventFactory.readFrom(in);
+ }
+
+ /** The event which we are forwarding. */
+ protected Event _event;
+}
diff --git a/src/java/com/threerings/presents/net/LogoffRequest.java b/src/java/com/threerings/presents/net/LogoffRequest.java
new file mode 100644
index 000000000..4f536070e
--- /dev/null
+++ b/src/java/com/threerings/presents/net/LogoffRequest.java
@@ -0,0 +1,23 @@
+//
+// $Id: LogoffRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+public class LogoffNotification extends UpstreamMessage
+{
+ /** The code for a logoff notification. */
+ public static final short TYPE = TYPE_BASE + 6;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public LogoffNotification ()
+ {
+ super();
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/java/com/threerings/presents/net/PingRequest.java b/src/java/com/threerings/presents/net/PingRequest.java
new file mode 100644
index 000000000..c8a7d1032
--- /dev/null
+++ b/src/java/com/threerings/presents/net/PingRequest.java
@@ -0,0 +1,23 @@
+//
+// $Id: PingRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+public class PingNotification extends UpstreamMessage
+{
+ /** The code for a ping notification. */
+ public static final short TYPE = TYPE_BASE + 5;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public PingNotification ()
+ {
+ super();
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+}
diff --git a/src/java/com/threerings/presents/net/SubscribeRequest.java b/src/java/com/threerings/presents/net/SubscribeRequest.java
new file mode 100644
index 000000000..dfa70dd5b
--- /dev/null
+++ b/src/java/com/threerings/presents/net/SubscribeRequest.java
@@ -0,0 +1,56 @@
+//
+// $Id: SubscribeRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class SubscribeRequest extends UpstreamMessage
+{
+ /** The code for an object subscription request. */
+ public static final short TYPE = TYPE_BASE + 1;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public SubscribeRequest ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a subscribe request for the distributed object with the
+ * specified object id.
+ */
+ public SubscribeRequest (int oid)
+ {
+ _oid = oid;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeInt(_oid);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _oid = in.readInt();
+ }
+
+ /**
+ * The object id of the distributed object to which we are
+ * subscribing.
+ */
+ protected int _oid;
+}
diff --git a/src/java/com/threerings/presents/net/UnsubscribeRequest.java b/src/java/com/threerings/presents/net/UnsubscribeRequest.java
new file mode 100644
index 000000000..3e25f95e1
--- /dev/null
+++ b/src/java/com/threerings/presents/net/UnsubscribeRequest.java
@@ -0,0 +1,56 @@
+//
+// $Id: UnsubscribeRequest.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+public class UnsubscribeNotification extends UpstreamMessage
+{
+ /** The code for an unsubscribe notification. */
+ public static final short TYPE = TYPE_BASE + 3;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public UnsubscribeNotification ()
+ {
+ super();
+ }
+
+ /**
+ * Constructs a unsubscribe notification for the distributed object
+ * with the specified object id.
+ */
+ public UnsubscribeNotification (int oid)
+ {
+ _oid = oid;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeInt(_oid);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ _oid = in.readInt();
+ }
+
+ /**
+ * The object id of the distributed object from which we are
+ * unsubscribing.
+ */
+ protected int _oid;
+}
diff --git a/src/java/com/threerings/presents/net/UpstreamMessage.java b/src/java/com/threerings/presents/net/UpstreamMessage.java
new file mode 100644
index 000000000..6e80787bc
--- /dev/null
+++ b/src/java/com/threerings/presents/net/UpstreamMessage.java
@@ -0,0 +1,105 @@
+//
+// $Id: UpstreamMessage.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.IOException;
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+
+import com.samskivert.cocktail.cher.io.TypedObject;
+import com.samskivert.cocktail.cher.io.TypedObjectFactory;
+
+/**
+ * The UpstreamMessage class encapsulates a message in the
+ * Distributed Object Protocol that flows from the client to the server.
+ * Upstream messages include object subscription, event forwarding and
+ * session management.
+ */
+public abstract class UpstreamMessage extends TypedObject
+{
+ /**
+ * All upstream message derived classes should base their typed object
+ * code on this base value.
+ */
+ public static final short TYPE_BASE = 100;
+
+ /**
+ * This is a unique (within the context of a reasonable period of
+ * time) identifier assigned to each upstream message. The message ids
+ * are used to correlate a downstream response message to the
+ * appropriate upstream request message.
+ */
+ public short messageId;
+
+ /**
+ * Each upstream message derived class must provide a zero argument
+ * constructor so that the TypedObjectFactory can create
+ * a new instance of said class prior to unserializing it.
+ */
+ public UpstreamMessage ()
+ {
+ // automatically generate a valid message id; on the client, this
+ // will be used, on the server it will be overwritten by the
+ // unserialized value
+ this.messageId = nextMessageId();
+ }
+
+ /**
+ * Derived classes should override this function to write their fields
+ * out to the supplied data output stream. They must be sure
+ * to first call super.writeTo().
+ */
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ super.writeTo(out);
+ out.writeShort(messageId);
+ }
+
+ /**
+ * Derived classes should override this function to read their fields
+ * from the supplied data input stream. They must be sure to
+ * first call super.readFrom().
+ */
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ super.readFrom(in);
+ messageId = in.readShort();
+ }
+
+ /**
+ * Returns the next message id suitable for use by an upstream
+ * message.
+ */
+ protected static synchronized short nextMessageId ()
+ {
+ _nextMessageId = (short)((_nextMessageId + 1) % Short.MAX_VALUE);
+ return _nextMessageId;
+ }
+
+ /**
+ * This is used to generate monotonically increasing message ids on
+ * the client as new messages are generated.
+ */
+ protected static short _nextMessageId;
+
+ // register our upstream message classes
+ static {
+ TypedObjectFactory.registerClass(AuthenticationRequest.TYPE,
+ AuthenticationRequest.class);
+ TypedObjectFactory.registerClass(SubscribeRequest.TYPE,
+ SubscribeRequest.class);
+ TypedObjectFactory.registerClass(FetchRequest.TYPE,
+ FetchRequest.class);
+ TypedObjectFactory.registerClass(UnsubscribeNotification.TYPE,
+ UnsubscribeNotification.class);
+ TypedObjectFactory.registerClass(ForwardEventNotification.TYPE,
+ ForwardEventNotification.class);
+ TypedObjectFactory.registerClass(PingNotification.TYPE,
+ PingNotification.class);
+ TypedObjectFactory.registerClass(LogoffNotification.TYPE,
+ LogoffNotification.class);
+ }
+}
diff --git a/src/java/com/threerings/presents/net/UsernamePasswordCreds.java b/src/java/com/threerings/presents/net/UsernamePasswordCreds.java
new file mode 100644
index 000000000..bfeac031a
--- /dev/null
+++ b/src/java/com/threerings/presents/net/UsernamePasswordCreds.java
@@ -0,0 +1,52 @@
+//
+// $Id: UsernamePasswordCreds.java,v 1.1 2001/05/22 06:08:00 mdb Exp $
+
+package com.samskivert.cocktail.cher.net;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.IOException;
+
+public class UsernamePasswordCreds
+{
+ public static final short TYPE = TYPE_BASE + 0;
+
+ /**
+ * Zero argument constructor used when unserializing an instance.
+ */
+ public UsernamePasswordCreds ()
+ {
+ super();
+ }
+
+ /**
+ * Construct credentials with the supplied username and password.
+ */
+ public UsernamePasswordCreds (String username, String password)
+ {
+ _username = username;
+ _password = password;
+ }
+
+ public short getType ()
+ {
+ return TYPE;
+ }
+
+ public void writeTo (DataOutputStream out)
+ throws IOException
+ {
+ out.writeUTF(_username);
+ out.writeUTF(_password);
+ }
+
+ public void readFrom (DataInputStream in)
+ throws IOException
+ {
+ _username = in.readUTF();
+ _password = in.readUTF();
+ }
+
+ protected String _username;
+ protected String _password;
+}