Initial revision

rkc10 [2002-05-17 23:03:45]
Initial revision
Filename
siena/ipaq/AttributeConstraint.java
siena/ipaq/AttributeValue.java
siena/ipaq/Covering.java
siena/ipaq/DirectSENPInterface.java
siena/ipaq/Filter.java
siena/ipaq/GenericSenderFactory.java
siena/ipaq/HierarchicalDispatcher.java
siena/ipaq/InvalidSenderException.java
siena/ipaq/KAPacketReceiver.java
siena/ipaq/KAPacketSender.java
siena/ipaq/Logging.java
siena/ipaq/Monitor.java
siena/ipaq/Notifiable.java
siena/ipaq/Notification.java
siena/ipaq/NotificationBuffer.java
siena/ipaq/Op.java
siena/ipaq/PacketReceiver.java
siena/ipaq/PacketReceiverClosed.java
siena/ipaq/PacketReceiverException.java
siena/ipaq/PacketReceiverFatalError.java
siena/ipaq/PacketSender.java
siena/ipaq/PacketSenderException.java
siena/ipaq/PacketSenderFactory.java
siena/ipaq/Pattern.java
siena/ipaq/SENP.java
siena/ipaq/SENPInvalidFormat.java
siena/ipaq/SENPPacket.java
siena/ipaq/Siena.java
siena/ipaq/SienaException.java
siena/ipaq/SienaId.java
siena/ipaq/StartServer.java
siena/ipaq/TCPPacketReceiver.java
siena/ipaq/TCPPacketSender.java
siena/ipaq/ThinClient.java
siena/ipaq/TimeoutExpired.java
siena/ipaq/UDPPacketReceiver.java
siena/ipaq/UDPPacketSender.java
siena/ipaq/error.txt
siena/ipaq/grep
diff --git a/siena/ipaq/AttributeConstraint.java b/siena/ipaq/AttributeConstraint.java
new file mode 100644
index 0000000..730d588
--- /dev/null
+++ b/siena/ipaq/AttributeConstraint.java
@@ -0,0 +1,135 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.lang.Integer;
+import java.lang.String;
+
+/** an elementary predicate over an attribute in an event notification.
+ *
+ *  <code>AttributeConstraint</code>s are the basic elements of a
+ *  {@link Filter}.  <p>
+ *
+ *  An <code>AttributeConstraint</code> is defined by an
+ *  <em>operator</em> and a <em>value</em>, and it is associated with
+ *  an attribute <em>name</em>.  Applying an
+ *  <code>AttributeConstraint</code> with operator <em>op</em> and
+ *  value <em>v</em> to an attribute value <em>x</em> means computing
+ *  <em>x op v</em>.  <p>
+ *
+ *  The operators provided by <code>AttributeConstraint</code> are
+ *  defined in {@link Op}.  They are <em>equal</em>,
+ *  <em>not-equal</em>, <em>less-than</em>, <em>less-or-equal</em>,
+ *  <em>greater-than</em>,<em>greater-or-equal</em>,
+ *  <em>has-substring</em>, <em>has-prefix</em>, <em>has-suffix</em>,
+ *  and <em>any</em>.
+ *
+ *  @see Op
+ *  @see Filter
+ **/
+public class AttributeConstraint {
+    /** the comparison value */
+    public AttributeValue	value;
+
+    /** the comparison operator
+     *
+     *  valid values are defined in {@link Op}
+     **/
+    public short		op;
+
+    /** creates a (deep) copy of an attribute constraint */
+    public AttributeConstraint(AttributeConstraint c) {
+	value = new AttributeValue(c.value);
+	op = c.op;
+    }
+
+    /** create an equality constraint with the given value */
+    public AttributeConstraint(AttributeValue v) {
+	value = v;
+	op = Op.EQ;
+    }
+
+    /** create a constraint with the given string value */
+    public AttributeConstraint(short o, String s) {
+	value = new AttributeValue(s);
+	op = o;
+    }
+
+    /** create a constraint with the given byte[] value */
+    public AttributeConstraint(short o, byte[] s) {
+	value = new AttributeValue(s);
+	op = o;
+    }
+
+    /** create a constraint with the given int value */
+    public AttributeConstraint(short o, int i) {
+	value = new AttributeValue(i);
+	op = o;
+    }
+
+    /** create a constraint with the given long value */
+    public AttributeConstraint(short o, long i) {
+	value = new AttributeValue(i);
+	op = o;
+    }
+
+    /** create a constraint with the given long value */
+    public AttributeConstraint(short o, double d) {
+	value = new AttributeValue(d);
+	op = o;
+    }
+
+    /** create a constraint with the given boolean value */
+    public AttributeConstraint(short o, boolean b) {
+	value = new AttributeValue(b);
+	op = o;
+    }
+
+    /** create a constraint with the given value */
+    public AttributeConstraint(short o, AttributeValue x) {
+	value = new AttributeValue(x);
+	op = o;
+    }
+
+    public boolean isEqualTo(AttributeConstraint x) {
+	//
+	// this is a conservative implementation.
+	//
+	return op == x.op && (op == Op.ANY || value.isEqualTo(x.value));
+    }
+
+    public String toString() {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	return new String(b.buf, 0, b.length());
+    }
+
+    public int hashCode() {
+	return toString().hashCode();
+    }
+}
diff --git a/siena/ipaq/AttributeValue.java b/siena/ipaq/AttributeValue.java
new file mode 100644
index 0000000..277cfcf
--- /dev/null
+++ b/siena/ipaq/AttributeValue.java
@@ -0,0 +1,257 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/**
+ *   Value of an attribute in an event notification.
+ *
+ *   An <code>AttributeValue</code> is a container for a typed vaule of
+ *   an attribute in a notification.  An <code>AttributeValue</code> can
+ *   be of type <code>String</code>, <code>byte[]</code>,
+ *   <code>int</code>, <code>long</code>, <code>double</code>, and
+ *   <code>boolean</code>.  <p>
+ *
+ *   Example:
+ *
+ *   <pre><code>
+ *       AttributeValue v = new
+ *       AttributeValue("Antonio");
+ *       System.out.println(v.stringValue());
+ *       Notification e = new Notification();
+ *       e.putAttribute("name", v);
+ *   </pre></code>
+ *
+ *   @see Notification
+ *   @see AttributeConstraint
+ **/
+public class AttributeValue implements java.io.Serializable {
+    /** <em>null</em> type, the default type of a Siena attribute */
+    public static final int	NULL		= 0;
+
+    /** string of bytes */
+    public static final int	BYTEARRAY	= 1;
+
+    /** string of bytes
+     *
+     *  an alias to <code>BYTEARRAY</code>
+     *	provided only for backward compatibility
+     **/
+    public static final int	STRING		= 1;
+
+    /** integer type.
+     *
+     *  corresponds to the Java <code>long</code> type.
+     **/
+    public static final int	LONG		= 2;
+
+    /** integer type.
+     *
+     *	corresponds to the Java <code>int</code> type.
+     **/
+    public static final int	INT		= 2;
+
+    /** double type.
+     *
+     *	corresponds to the Java <code>double</code> type.
+     **/
+    public static final int	DOUBLE		= 3;
+
+    /** boolean type.
+     *
+     *	corresponds to the Java <code>boolean</code> type.
+     **/
+    public static final int	BOOL		= 4;
+
+    private	int		type;
+
+    private	byte[]		sval;
+    private	long		ival;
+    private	double		dval;
+    private	boolean		bval;
+    // other types here...
+
+    public AttributeValue() {
+	type = NULL;
+    }
+
+    public AttributeValue(AttributeValue x) {
+	if (x == null) {
+	    type = NULL;
+	    return;
+	}
+	type = x.type;
+	switch(type) {
+	case INT: ival = x.ival; break;
+	case BOOL: bval = x.bval; break;
+	case DOUBLE: dval = x.dval; break;
+	case BYTEARRAY: sval = (byte[])x.sval.clone(); break;
+	}
+    }
+
+    public AttributeValue(String s) {
+	if (s == null) {
+	    type = NULL;
+	    return;
+	}
+	type = BYTEARRAY;
+	sval = s.getBytes();
+    }
+
+    public AttributeValue(byte[] s) {
+	type = BYTEARRAY;
+	sval = (byte[])s.clone();
+    }
+
+    public AttributeValue(long i) {
+	type = LONG;
+	ival = i;
+	sval = null;
+    }
+
+    public AttributeValue(boolean b) {
+	type = BOOL;
+	bval = b;
+	sval = null;
+    }
+
+    public AttributeValue(double d) {
+	type = DOUBLE;
+	dval = d;
+	sval = null;
+    }
+    //
+    // other types here ...work in progress...
+    //
+
+    public int getType() {
+	return type;
+    }
+
+    public int intValue() {
+	switch(type) {
+	case LONG: return (int)ival;
+	case BOOL: return bval ? 1 : 0;
+	case DOUBLE: return (int)dval;
+	    //
+	    // perhaps I should use Integer.decode() instead of
+	    // Integer.valueOf().  Anybody knows the difference?
+	    //
+	case BYTEARRAY: return Integer.valueOf(new String(sval)).intValue();
+	default:
+	    return 0; // should throw an exception here
+	              // ...work in progress...
+	}
+    }
+
+    public long longValue() {
+	switch(type) {
+	case LONG: return ival;
+	case BOOL: return bval ? 1 : 0;
+	case DOUBLE: return (int)dval;
+	    //
+	    // Same as above. What's the difference between
+	    // Long.valueOf() and Long.decode()?
+	    //
+	case BYTEARRAY: return Long.valueOf(new String(sval)).longValue();
+	default:
+	    return 0; // should throw an exception here
+	              // ...work in progress...
+	}
+    }
+
+    public double doubleValue() {
+	switch(type) {
+	case LONG: return ival;
+	case BOOL: return bval ? 1 : 0;
+	case DOUBLE: return dval;
+	case BYTEARRAY: return Double.valueOf(new String(sval)).doubleValue();
+	default:
+	    return 0; // should throw an exception here
+	              // ...work in progress...
+	}
+    }
+
+    public boolean booleanValue() {
+	switch(type) {
+	case LONG: return ival != 0;
+	case BOOL: return bval;
+	case DOUBLE: return dval != 0;
+	case BYTEARRAY: return Boolean.valueOf(new String(sval)).booleanValue();
+	default:
+	    return false; // should throw an exception here
+	                  // ...work in progress...
+	}
+    }
+
+    public String stringValue() {
+	switch(type) {
+	case LONG: return String.valueOf(ival);
+	case BOOL: return String.valueOf(bval);
+	case DOUBLE: return String.valueOf(dval);
+	case BYTEARRAY: return new String(sval);
+	default:
+	    return ""; // should throw an exception here
+	               // ...work in progress...
+	}
+    }
+
+    public byte[] byteArrayValue() {
+	switch(type) {
+	case LONG: return String.valueOf(ival).getBytes();
+	case BOOL: return String.valueOf(bval).getBytes();
+	case DOUBLE: return String.valueOf(dval).getBytes();
+	case BYTEARRAY: return sval;
+	default:
+	    return null; // should throw an exception here
+	                 // ...work in progress...
+	}
+    }
+
+    public boolean isEqualTo(AttributeValue x) {
+        switch(type) {
+        case BYTEARRAY: return sval.equals(x.sval);
+        case LONG: return ival == x.longValue();
+        case DOUBLE: return dval == x.doubleValue();
+        case BOOL: return bval == x.booleanValue();
+        default: return false;
+        }
+    }
+
+    public String toString() {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	return new String(b.buf, 0, b.length());
+    }
+
+    public int hashCode() {
+	return this.toString().hashCode();
+    }
+}
+
+
+
diff --git a/siena/ipaq/Covering.java b/siena/ipaq/Covering.java
new file mode 100644
index 0000000..3f5c8d3
--- /dev/null
+++ b/siena/ipaq/Covering.java
@@ -0,0 +1,379 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This program is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU General Public License
+//  as published by the Free Software Foundation; either version 2
+//  of the License, or (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+// import com.sun.java.util.collections.Iterator;
+// import com.sun.java.util.collections.List;
+// import com.sun.java.util.collections.Set;
+// import com.sun.java.util.collections.HashSet;
+// import com.sun.java.util.collections.Map.Entry;
+//
+// for some misterious reason, importing single classes doesn't work
+// here.  Go figure.
+//
+import com.sun.java.util.collections.*;
+//import java.util.*;
+
+/** implementation of the covering relations.
+
+    <code>Covering</code> implements the covering relations that
+    determine the semantics of </em>Siena</em>.  This class is used
+    internally by the implementation of Siena.  However, it is also
+    provided as a public class in the siena package for convenience.
+**/
+public class Covering {
+
+    /** semantics of operators in filters.
+
+	<code>apply_operator(op, x, y)</code> is equivalent to <em>x
+        op y</em>
+    **/
+    public static boolean apply_operator(short op,
+					 AttributeValue x, AttributeValue y) {
+	switch(op) {
+	case Op.ANY:
+	    return true;
+	case Op.EQ:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().equals(y.stringValue());
+	    case AttributeValue.BOOL:
+		return y.getType() == AttributeValue.BOOL
+		    && x.booleanValue() == y.booleanValue();
+	    case AttributeValue.INT:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() == y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() == y.doubleValue());
+	    case AttributeValue.DOUBLE:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() == y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() == y.doubleValue());
+	    case AttributeValue.NULL:
+		return (y.getType() == AttributeValue.NULL);
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	case Op.NE:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() != AttributeValue.STRING
+		    || ! x.stringValue().equals(y.stringValue());
+	    case AttributeValue.BOOL: return y.getType() != AttributeValue.BOOL
+					  || x.booleanValue() != y.booleanValue();
+	    case AttributeValue.INT:
+		switch(y.getType()) {
+		case AttributeValue.INT:
+		    return x.doubleValue() != y.doubleValue();
+		case AttributeValue.DOUBLE:
+		    return x.doubleValue() != y.doubleValue();
+		default: return true;
+		}
+	    case AttributeValue.DOUBLE:
+		switch(y.getType()) {
+		case AttributeValue.INT:
+		    return x.doubleValue() != y.doubleValue();
+		case AttributeValue.DOUBLE:
+		    return x.doubleValue() != y.doubleValue();
+		default: return true;
+		}
+	    case AttributeValue.NULL:
+		return (y.getType() != AttributeValue.NULL);
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	case Op.SS:
+	    switch (x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().indexOf(y.stringValue()) != -1;
+	    default: return false;			// I should probably
+	    }						// throw an exception
+	case Op.SF:
+	    switch (x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().endsWith(y.stringValue());
+	    default: return false;			// I should probably
+	    }						// throw an exception
+	case Op.PF:
+	    switch (x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().startsWith(y.stringValue());
+	    default: return false;			// I should probably
+	    }						// throw an exception
+	case Op.LT:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+				   && x.stringValue().compareTo(y.stringValue()) < 0;
+	    case AttributeValue.INT:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() < y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() < y.doubleValue());
+	    case AttributeValue.BOOL:
+		return y.getType() == AttributeValue.BOOL
+		    && !x.booleanValue() && y.booleanValue();
+	    case AttributeValue.DOUBLE:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() < y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() < y.doubleValue());
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	case Op.GT:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().compareTo(y.stringValue()) > 0;
+	    case AttributeValue.INT:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() > y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() > y.doubleValue());
+	    case AttributeValue.BOOL:
+		return y.getType() == AttributeValue.BOOL
+		    && x.booleanValue() && !y.booleanValue();
+	    case AttributeValue.DOUBLE:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() > y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() > y.doubleValue());
+	    case AttributeValue.NULL:
+		//
+		// I'm not sure about the ``right'' semantics here
+		// ...work in progress...
+		//
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	case Op.LE:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().compareTo(y.stringValue()) <= 0;
+	    case AttributeValue.INT:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() <= y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() <= y.doubleValue());
+	    case AttributeValue.BOOL:
+		return y.getType() == AttributeValue.BOOL
+		    && (!x.booleanValue() || y.booleanValue());
+	    case AttributeValue.DOUBLE:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() <= y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() <= y.doubleValue());
+	    case AttributeValue.NULL:
+		//
+		// I'm not sure about the ``right'' semantics here
+		// ...work in progress...
+		//
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	case Op.GE:
+	    switch(x.getType()) {
+	    case AttributeValue.STRING:
+		return y.getType() == AttributeValue.STRING
+		    && x.stringValue().compareTo(y.stringValue()) >= 0;
+	    case AttributeValue.INT:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() >= y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() >= y.doubleValue());
+	    case AttributeValue.BOOL:
+		return y.getType() == AttributeValue.BOOL
+		    && (x.booleanValue() || !y.booleanValue());
+	    case AttributeValue.DOUBLE:
+		return (y.getType() == AttributeValue.INT
+			&& x.doubleValue() >= y.doubleValue())
+		    || (y.getType() == AttributeValue.DOUBLE
+			&& x.doubleValue() >= y.doubleValue());
+	    case AttributeValue.NULL:
+		//
+		// I'm not sure about the ``right'' semantics here
+		// ...work in progress...
+		//
+	    default:					// I should probably
+		return false;				// throw an exception
+	    }
+	default:
+	    return false;				// exception ?
+	}
+    }
+
+    /** covering between two attribute constraints.
+
+	<em>true</em> when <code>af1</code> defines a set of attribute
+        values <em>S</em><sub>af1</sub> that contains the set of
+        attribute values <em>S</em><sub>af2</sub> defined by
+        <code>af2</code>, in other words true <em>iff af2 ==&gt;
+        af1</em>, i.e., for every <code>AttributeValue</code>
+        <em>x</em>: <em>x op2 f2 ==&gt; x op1 f1</em> where <em>op2</em>
+        is the operator defined by af2 and <em>f2</em> is the value
+        defined by af2. Same thing for af1.
+    */
+    public static boolean covers(AttributeConstraint af1,
+				 AttributeConstraint af2) {
+	// WARNING!
+	// this is a crucial function! it is also quite tricky, think
+	// twice before you change this implementation!
+	//
+	// All Siena operators define transitive relations, except for
+	// NE.
+	//
+	// trivial cases
+	//
+	// {x any} covers everything
+	//
+	if (af1.op == Op.ANY) return true;
+	//
+	// nothing covers {x any} (except {x any}, see above)
+	//
+	if (af2.op == Op.ANY) return false;
+	//
+	// {x != a} C {x op b} <-- not a op b
+	//
+	if (af1.op == Op.NE)
+	    return !apply_operator(af2.op,af1.value, af2.value);
+	//
+	// same operators (we already excluded af1.op == NE)
+	//
+	if (af2.op == af1.op)
+	    return apply_operator(af1.op, af2.value, af1.value)
+		|| apply_operator(Op.EQ, af2.value, af1.value);
+	//
+	// else I must consider the implications between DIFFERENT operators
+	//
+	switch(af2.op) {
+	case Op.EQ: return apply_operator(af1.op, af2.value, af1.value);
+	case Op.LT: return af1.op == Op.LE
+		     && apply_operator(Op.LT, af2.value, af1.value);
+	case Op.LE: return af1.op == Op.LT
+		     && apply_operator(Op.GT, af1.value, af2.value);
+	case Op.GT: return af1.op == Op.GE
+		     && apply_operator(Op.LE, af1.value, af2.value);
+	case Op.GE: return af1.op == Op.GT
+		     && apply_operator(Op.LT, af1.value, af2.value);
+	case Op.SF: return af1.op == Op.SS
+		     && apply_operator(Op.SS, af2.value , af1.value);
+	case Op.PF:
+	    switch(af1.op) {
+	    case Op.SS:
+		return apply_operator(Op.SS, af2.value,af1.value);
+	    case Op.GT:
+		return apply_operator(Op.LE, af1.value, af2.value)
+		    && !apply_operator(Op.PF, af1.value, af2.value);
+	    case Op.LT:
+		return apply_operator(Op.GE, af1.value, af2.value)
+		    && !apply_operator(Op.PF, af1.value, af2.value);
+	    case Op.GE:
+		return apply_operator(Op.LT, af1.value, af2.value);
+	    case Op.LE:
+		return apply_operator(Op.GT, af1.value, af2.value);
+	    default: return false;
+	    }
+	default: return false;
+	}
+    }
+
+    public static boolean apply(AttributeConstraint ac, AttributeValue av) {
+	return apply_operator(ac.op, av, ac.value);
+    }
+
+    /**
+     *  semantics of subscriptions
+     */
+    public static boolean apply(Filter f, Notification e) {
+	for(Iterator i = f.constraints.entrySet().iterator(); i.hasNext();) {
+	    Map.Entry entry = (Map.Entry)i.next();
+	    String name = (String)entry.getKey();
+	    AttributeValue ea = (AttributeValue)e.getAttribute(name);
+	    if (ea == null) return false;
+	    for (Iterator li = ((Set)entry.getValue()).iterator();
+		 li.hasNext();)
+		if (!apply((AttributeConstraint)li.next(), ea))
+		    return false;
+	}
+	return true;
+    }
+
+    /** covering relation between two filters.
+
+	true iff for all notifications <em>n</em>: apply(f2,n) ==&gt;
+	apply(f1,n)
+    */
+    public static boolean covers(Filter f1, Filter f2)
+    {
+	//
+	// true iff f2 ==> f1
+	// I think this expression translates into:
+	// for each attribute filter af1 in f1, there exist at least one
+	// corresponding (same name) attribute filter af2 in f2 such that
+	// af1 covers af2
+	//
+	// I'm not 100% sure of the demonstration though, the idea is that
+	// attribute filters define ``connected'' subsets of ``ordered''
+	// sets (which has changed since I added NE)... I think I
+	// should also assume that f2 is not `null' (i.e., contradictory)
+	// ...work in progress...
+	//
+	Iterator fi1;
+	Iterator fi2;
+	for(fi1 = f1.constraints.entrySet().iterator(); fi1.hasNext();) {
+	    Map.Entry fe1 = (Map.Entry)fi1.next();
+	    for(Iterator i = ((Set)fe1.getValue()).iterator(); i.hasNext();) {
+		    boolean found = false;
+		    Iterator i2 = f2.constraintsIterator((String)fe1.getKey());
+		    if (i2 == null) return false;
+		    AttributeConstraint c = (AttributeConstraint)i.next();
+		    while(!found && i2.hasNext())
+			found = covers(c, (AttributeConstraint)i2.next());
+		    if (!found) return false;
+		}
+	}
+	return true;
+    }
+
+    /** covering relation between two patterns.
+     */
+    public static boolean covers(Pattern p1, Pattern p2) {
+	if (p1.filters.length != p2.filters.length) return false;
+	for(int i = 0; i < p1.filters.length; ++i)
+	    if (!covers(p1.filters[i], p2.filters[i]))
+		return false;
+	return true;
+    }
+}
diff --git a/siena/ipaq/DirectSENPInterface.java b/siena/ipaq/DirectSENPInterface.java
new file mode 100644
index 0000000..d8a90c6
--- /dev/null
+++ b/siena/ipaq/DirectSENPInterface.java
@@ -0,0 +1,281 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** low-level interface to the Siena event notification service.
+ **/
+public class DirectSENPInterface {
+
+    private PacketSender	master;
+    private byte[]		master_handler;
+
+    PacketSenderFactory		sender_factory;
+    static PacketSenderFactory	default_sender_factory
+						= new GenericSenderFactory();
+
+    /** sets the packet-sender factory associated with this
+     *  SENP interface
+     *
+     *  the packet sender factory is used every time this interface is
+     *  connected to a new master server through {@link
+     *  #setServer(String)}
+     *
+     *  @see #setDefaultPacketSenderFactory(PacketSenderFactory)
+     **/
+    public void setPacketSenderFactory(PacketSenderFactory f) {
+	sender_factory = f;
+    }
+
+    /** default packet-sender factory for DireactSENPInterface
+     *  objects
+     *
+     *  every new object is assigned this factory
+     *
+     *  @see #setPacketSenderFactory(PacketSenderFactory)
+     **/
+    static public void setDefaultPacketSenderFactory(PacketSenderFactory f) {
+	default_sender_factory = f;
+    }
+
+    /** creates and connects to a given Siena server.
+     *
+     *	@param server the uri of the server to connect to
+     *                (e.g., "senp://host.domain.net:7654")
+     **/
+    public DirectSENPInterface(String server) throws InvalidSenderException {
+	sender_factory = default_sender_factory;
+	master = sender_factory.createPacketSender(server);
+	master_handler = server.getBytes();
+    }
+
+    /** connectes to a given Siena server.
+     *
+     *	@param server the uri of the server to connect to
+     *                (e.g., "senp://host.domain.net:7654")
+     **/
+    public void setServer(String server) throws InvalidSenderException {
+	master = sender_factory.createPacketSender(server);
+	master_handler = server.getBytes();
+    }
+
+    /** suspends the delivery of notifications for a given subscriber.
+     *
+     *	This causes the Siena server to stop sending notification to
+     *	the given subscriber.  The server correctly maintains all the
+     *	existing subscriptions so that the flow of notification can be
+     *	later resumed (with {@link #resume(String)}).
+     *
+     *	@param id identity of the subscriber
+     *	@see #resume(String)
+     **/
+    public void suspend(String id) throws SienaException {
+	if (id == null) return;
+	SENPPacket sus = new SENPPacket();
+	sus.id = id.getBytes();
+	sus.method = SENP.SUS;
+	sus.to = master_handler;
+	sus.encode();
+	master.send(sus.buf, 0, sus.length());
+    }
+
+    /** resumes the delivery of notifications for a given subscriber
+     *
+     *	This causes the Siena (master) server to resume sending
+     *	notification to the given subscriber.
+     *
+     *	@see #suspend(String)
+     **/
+    public void resume(String id) throws SienaException {
+	if (id == null) return;
+	SENPPacket sus = new SENPPacket();
+	sus.id = id.getBytes();
+	sus.method = SENP.RES;
+	sus.to = master_handler;
+	sus.encode();
+	master.send(sus.buf, 0, sus.length());
+    }
+
+    public void unsubscribeAll(String id) throws SienaException {
+	if (id == null) return;
+	SENPPacket pkt = new SENPPacket();
+	pkt.id = id.getBytes();
+	pkt.method = SENP.BYE;
+	pkt.to = master_handler;
+	pkt.encode();
+	master.send(pkt.buf, 0, pkt.length());
+    }
+
+    public void publish(Notification n) throws SienaException {
+	if (n == null) return;
+	SENPPacket req = new SENPPacket();
+	req.event = n;
+	req.method = SENP.PUB;
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void subscribe(Filter f, String id, String handler)
+	throws SienaException {
+	if (handler == null || f == null || id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.filter = f;
+	req.method = SENP.SUB;
+	req.id = id.getBytes();
+	req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void subscribe(Pattern p, String id, String handler)
+	throws SienaException {
+	if (handler == null || p == null || id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.pattern = p;
+	req.method = SENP.SUB;
+	req.id = id.getBytes();
+	req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void unsubscribe(Filter f, String id, String handler)
+	throws SienaException {
+	if (handler == null || f == null || id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.filter = f;
+	req.method = SENP.UNS;
+	req.id = id.getBytes();
+	req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void unsubscribe(Pattern p, String id, String handler)
+	throws SienaException {
+	if (handler == null || p == null || id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.pattern = p;
+	req.method = SENP.UNS;
+	req.id = id.getBytes();
+	req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void mapHandler(String id, String handler)
+	throws SienaException {
+	if (handler == null || id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.method = SENP.MAP;
+	req.id = id.getBytes();
+	req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    /** switch to a different master server (possibly null)
+     **/
+    public void configure(String handler)
+	throws SienaException {
+	SENPPacket req = new SENPPacket();
+	req.method = SENP.CNF;
+	req.id = null;
+	if (handler != null)
+	    req.handler = handler.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void shutdown()
+	throws SienaException {
+	SENPPacket req = new SENPPacket();
+	req.method = SENP.OFF;
+	req.id = null;
+	req.handler = null;
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void advertise(Filter f, String id) throws SienaException {
+	if (id == null || f == null) return;
+
+	SENPPacket req = new SENPPacket();
+	req.filter = f;
+	req.method = SENP.ADV;
+	req.id = id.getBytes();
+	req.to = master_handler;
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void unadvertise(Filter f, String id) throws SienaException {
+	if (id == null) return;
+	SENPPacket req = new SENPPacket();
+	req.id = id.getBytes();
+	req.to = master_handler;
+	req.method = SENP.UNA;
+	req.filter = f;
+	//
+	// should I have a special method for UNA all (when f == null)?
+	// ...work in progress...
+	//
+	req.encode();
+	master.send(req.buf, 0, req.length());
+    }
+
+    public void unadvertiseAll(String id) throws SienaException {
+	unadvertise(null, id);
+    }
+
+    static public void processNotification(Notifiable n,
+					   PacketReceiver r)
+	throws SienaException {
+	SENPPacket req = new SENPPacket();
+	int res;
+	res = r.receive(req.buf);
+	if (res > 0) {
+	    req.init(res);
+	    req.decode();
+	    if (req.ttl > 0 && req.method ==  SENP.PUB) {
+		if (req.event != null) {
+		    n.notify(req.event);
+		} else if (req.events != null) {
+		    n.notify(req.events);
+		}
+	    }
+	}
+    }
+}
diff --git a/siena/ipaq/Filter.java b/siena/ipaq/Filter.java
new file mode 100644
index 0000000..a52bbde
--- /dev/null
+++ b/siena/ipaq/Filter.java
@@ -0,0 +1,294 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import com.sun.java.util.collections.Set;
+import com.sun.java.util.collections.Map;
+import com.sun.java.util.collections.HashSet;
+import com.sun.java.util.collections.HashMap;
+import com.sun.java.util.collections.Iterator;
+
+/** a selection predicate defined over notifications.
+ *
+ *  The form of a <code>Filter</code> is a conjunction of
+ *  <em>constraints</em> (see {@link AttributeConstraint}).  Each
+ *  constraint poses an elementary condition on a specific attribute
+ *  of the event.  For example a contraint<em>[price &lt; 10]</em>
+ *  requires that the event contain an attribute named "price" whose
+ *  value is a number less than 10.  A <code>Filter</code> can have
+ *  more that one constraint for an attribute.  For example, a
+ *  <code>Filter</code> can express things like <em>[model="custom",
+ *  price &gt; 10, price &lt;= 20]</em>.<p>
+ *
+ *  Every <em>constraint</em> in a <code>Filter</code> implicitly
+ *  expresses an existential quantifier over the event.  The filter
+ *  of the previous example (<em>[model="custom", price &gt; 10,
+ *  price &lt;= 20]</em>) requires that an event contain an attribute
+ *  named "model", whose value is the string "custom", and an
+ *  attribute named "price" whose value is a number between 10 and 20
+ *  (20 included).<p>
+ *
+ *  The valid syntax for attribute names is the same for {@link
+ *  Notification}.<p>
+ *
+ *  @see AttributeConstraint
+ *  @see Notification
+ **/
+public class Filter implements java.io.Serializable {
+    //
+    // what I really need here is a multimap, but there is no such
+    // thing in java.util (JDK 1.2.2), so I'm going to implement it as
+    // a map of lists
+    //
+    Map constraints;
+
+    /** creates an empty filter.
+     *
+     *	creates a filter with no constraints. Notice that an empty
+     *	filter does not match any notification.
+     **/
+    public Filter() {
+	constraints = new HashMap();
+    }
+
+    /** creates a (deep) copy of a given filter.
+     **/
+    public Filter(Filter f) {
+	//
+	// again, since Java doesn't have decent collections
+	// (multimaps), I have to do this really horrible copy.
+	//
+	constraints = new HashMap();
+	for(Iterator i = f.constraints.entrySet().iterator(); i.hasNext();) {
+	    Map.Entry entry = (Map.Entry)i.next();
+	    String name = (String)entry.getKey();
+	    Set alist = new HashSet();
+	    constraints.put(name,alist);
+	    for(Iterator li = ((Set)entry.getValue()).iterator();
+		li.hasNext();)
+		alist.add(new AttributeConstraint((AttributeConstraint)li.next()));
+	}
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out)
+	throws java.io.IOException {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	out.writeInt(b.length());
+	out.write(b.buf, 0, b.length());
+    }
+
+    private void readObject(java.io.ObjectInputStream in)
+	throws java.io.IOException, java.lang.ClassNotFoundException {
+	int len = in.readInt();
+	SENPBuffer b = new SENPBuffer();
+	in.readFully(b.buf, 0, len);
+	b.init(len);
+
+	Filter f;
+	try {
+	    f = b.decodeFilter();
+	} catch (SENPInvalidFormat ex) {
+	    throw new java.io.InvalidObjectException(ex.toString());
+	}
+	constraints = new HashMap();
+	for(Iterator i = f.constraints.entrySet().iterator(); i.hasNext();) {
+	    Map.Entry entry = (Map.Entry)i.next();
+	    String name = (String)entry.getKey();
+	    Set alist = new HashSet();
+	    constraints.put(name,alist);
+	    for(Iterator li = ((Set)entry.getValue()).iterator();
+		li.hasNext();)
+		alist.add(new AttributeConstraint((AttributeConstraint)li.next()));
+	}
+    }
+
+    /** <code>true</code> <em>iff</em> this filter contains no constraints
+     **/
+    public boolean isEmpty() {
+	return constraints.isEmpty();
+    }
+
+    /** puts a constraint <em>a</em> on attribute <em>name</em>.
+     *
+     *	Example:
+     *	<pre><code>
+     *	    Filter f = new Filter();
+     *	    AttributeConstraint a;
+     *	    a = new AttrbuteConstraint(Op.SS, "soft")
+     *	    f.addConstraint("subject", a);
+     *	</pre></code>
+     **/
+    public void addConstraint(String name, AttributeConstraint a) {
+	Set s = (Set)constraints.get(name);
+	if (s == null) {
+	    s = new HashSet();
+	    constraints.put(name,s);
+	}
+	s.add(a);
+    }
+
+    /** puts a constraint on attribute <em>name</em> using
+     *	comparison operator <em>op</em> and a <code>String</code>
+     *	argument <em>sval</em>.
+     *
+     *	<pre><code>
+     *	    Filter f = new Filter();
+     *	    f.addConstraint("subject", Op.SS, "soft");
+     *	</pre></code>
+     **/
+    public void addConstraint(String s, short op, String sval) {
+	addConstraint(s,new AttributeConstraint(op, sval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using
+     *  comparison operator <em>op</em> and a <code>byte[]</code>
+     *  argument <em>sval</em>.
+     **/
+    public void addConstraint(String s, short op, byte[] sval) {
+	addConstraint(s,new AttributeConstraint(op, sval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using comparison
+     *  operator <em>op</em> and a <code>long</code> argument
+     *  <em>lval</em>.
+     **/
+    public void addConstraint(String s, short op, long lval) {
+	addConstraint(s,new AttributeConstraint(op, lval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using comparison
+     *	operator <em>op</em> and a <code>boolean</code> argument
+     *	<em>bval</em>.
+     *
+     *  <pre><code>
+     *      Filter f = new Filter();
+     *	    f.addConstraint("failed", Op.EQ, true);
+     *  </pre></code>
+     **/
+    public void addConstraint(String s, short op, boolean bval) {
+	addConstraint(s,new AttributeConstraint(op, bval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using comparison
+     *  operator <em>op</em> and a <code>double</code> argument
+     * <em>dval</em>.
+     **/
+    public void addConstraint(String s, short op, double dval) {
+	addConstraint(s,new AttributeConstraint(op, dval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using the
+     *  equality operator and a <code>String</code> argument
+     *  <em>sval</em>.
+     *
+     *  Example:
+     *  <pre><code>
+     *      Filter f = new Filter();
+     *      f.addConstraint("name", "Antonio");
+     *  </pre></code>
+     **/
+    public void addConstraint(String s, String sval) {
+	addConstraint(s,new AttributeConstraint(Op.EQ, sval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using the
+     *  equality operator and a <code>byte[]</code> argument
+     *  <em>sval</em>.
+     **/
+    public void addConstraint(String s, byte[] sval) {
+	addConstraint(s,new AttributeConstraint(Op.EQ, sval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using the
+     *  equality operator and a <code>boolean</code> argument
+     *  <em>bval</em>.
+     **/
+    public void addConstraint(String s, boolean bval) {
+	addConstraint(s,new AttributeConstraint(Op.EQ, bval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using the
+     *  equality operator and a <code>long</code> argument
+     *  <em>lval</em>.
+     **/
+    public void addConstraint(String s, long lval) {
+	addConstraint(s,new AttributeConstraint(Op.EQ, lval));
+    }
+
+    /** puts a constraint on attribute <em>name</em> using the
+     *  equality operator and a <code>double</code> argument
+     *  <em>dval</em>.
+     **/
+    public void addConstraint(String s, double dval) {
+	addConstraint(s,new AttributeConstraint(Op.EQ, dval));
+    }
+
+    /** returns true if this filter contains at least one constraint
+     *  for the specified attribute
+     **/
+    public boolean containsConstraint(String s) {
+	return constraints.containsKey(s);
+    }
+
+    /** removes all the constraints for the specified attribute.
+     *
+     *  @return true if any constraints existed and has been removed
+     **/
+    public boolean removeConstraints(String s) {
+	return constraints.remove(s) != null;
+    }
+
+    /** removes all constraints.
+     **/
+    public void clear() {
+	constraints.clear();
+    }
+
+    /** returns an iterator for the set of attribute (constraint)
+     *  names of this <code>Filter</code>.
+     **/
+    public Iterator constraintNamesIterator() {
+	return constraints.keySet().iterator();
+    }
+
+    /** returns an iterator for the set of constraints over attribute
+     *  <em>name</em> of this <code>Filter</code>.
+     **/
+    public Iterator constraintsIterator(String name) {
+	Set s = (Set)constraints.get(name);
+	if (s == null) return null;
+	return s.iterator();
+    }
+
+    public String toString() {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	return new String(b.buf, 0, b.length());
+    }
+}
diff --git a/siena/ipaq/GenericSenderFactory.java b/siena/ipaq/GenericSenderFactory.java
new file mode 100644
index 0000000..2148ec3
--- /dev/null
+++ b/siena/ipaq/GenericSenderFactory.java
@@ -0,0 +1,73 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.io.IOException;
+
+/** creates PacketSenders based on their URI
+ *
+ *  this factory recognizes "senp:" (see {@link TCPPacketReceiver}),
+ *  "udp+senp:" (see {@link UDPPacketReceiver}), and "ka+senp:" (see
+ *  {@link KAPacketReceiver}) schemas.
+ **/
+public class GenericSenderFactory implements PacketSenderFactory {
+    public PacketSender createPacketSender(String handler)
+	throws InvalidSenderException {
+	//
+	// this parses a handler of the form:
+	// <schema> "://" <host> [":" <port>] ["/" <path>]
+	//
+	int pos = handler.indexOf(":");
+	if (pos < 0) throw (new InvalidSenderException("can't find schema"));
+	String schema = handler.substring(0,pos);
+	if (schema.equals(TCPPacketReceiver.protocol_name)) {
+	    try {
+		return new TCPPacketSender(handler.substring(pos+1,
+							     handler.length()));
+	    } catch (IOException ex) {
+		throw new InvalidSenderException(ex.getMessage());
+	    }
+	} else if (schema.equals(UDPPacketReceiver.protocol_name)) {
+	    try {
+		return new UDPPacketSender(handler.substring(pos+1,
+							     handler.length()));
+	    } catch (IOException ex) {
+		throw new InvalidSenderException(ex.getMessage());
+	    }
+	} else if (schema.equals(KAPacketReceiver.protocol_name)) {
+	    try {
+		return new KAPacketSender(handler.substring(pos+1,
+							    handler.length()));
+	    } catch (IOException ex) {
+		throw new InvalidSenderException(ex.getMessage());
+	    }
+	} else {
+	    throw (new InvalidSenderException("unknown schema: " + schema));
+	}
+    }
+}
diff --git a/siena/ipaq/HierarchicalDispatcher.java b/siena/ipaq/HierarchicalDispatcher.java
new file mode 100644
index 0000000..7e9f86c
--- /dev/null
+++ b/siena/ipaq/HierarchicalDispatcher.java
@@ -0,0 +1,2003 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This program is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU General Public License
+//  as published by the Free Software Foundation; either version 2
+//  of the License, or (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+
+package siena;
+
+import com.sun.java.util.collections.Collection;
+import com.sun.java.util.collections.Set;
+import com.sun.java.util.collections.HashSet;
+import com.sun.java.util.collections.Map;
+import com.sun.java.util.collections.Map.Entry;
+import com.sun.java.util.collections.HashMap;
+import com.sun.java.util.collections.List;
+import com.sun.java.util.collections.LinkedList;
+import com.sun.java.util.collections.Iterator;
+import com.sun.java.util.collections.ListIterator;
+
+import java.io.IOException;
+import java.io.PrintStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+interface PacketNotifiable {
+    //
+    // returns false whenever the notification mechanism failed, and
+    // therefore this notifiable could be considered unreachable (am I
+    // making sense?  this sentence might sound a bit "italian", but I
+    // don't have time to fix it now)
+    //
+    public boolean notify(SENPPacket pkt);
+}
+
+//
+// this is the abstraction of the subscriber used by the
+// HierarchicalDispatcher.  It represents remote as well as local
+// notifiable objects.  In addition to that, this object keeps track
+// of failed attempts to contact the notifiable object so that
+// HierarchicalDispatcher can periodically clean up its subscriber
+// tables
+//
+class Subscriber implements PacketNotifiable {
+    public  short		failed_attempts = 0;
+    public  long		latest_good	= 0;
+
+    private boolean		suspended	= false;
+    private Notifiable		localobj	= null;
+    private PacketSender	remoteobj	= null;
+    int				refcount	= 0;
+    private SENPPacket		spkt		= new SENPPacket();
+    public final byte[]		identity;
+
+    public Subscriber(Notifiable n) {
+	localobj = n;
+	identity = null;
+	suspended = false;
+	latest_good = 0;
+	failed_attempts = 0;
+	refcount = 0;
+    }
+
+    public Subscriber(String id, Notifiable n) {
+	localobj = n;
+	identity = id.getBytes();
+	suspended = false;
+	latest_good = 0;
+	failed_attempts = 0;
+	refcount = 0;
+    }
+
+    public Subscriber(String id, PacketSender ps)
+    throws InvalidSenderException {
+	remoteobj = ps;
+	identity = id.getBytes();
+	suspended = false;
+	latest_good = 0;
+	failed_attempts = 0;
+	refcount = 0;
+    }
+
+    public Object getKey() {
+	if (identity != null) return new String(identity);
+	return localobj;
+    }
+
+    public boolean isLocal() {
+	return localobj != null;
+    }
+
+    synchronized public void addRef() {
+	++refcount;
+    }
+
+    synchronized public void removeRef() {
+	if (refcount > 0) --refcount;
+    }
+
+    synchronized public boolean hasNoRefs() {
+	return refcount == 0;
+    }
+
+    synchronized public void mapHandler(PacketSender ps)
+	throws InvalidSenderException {
+	remoteobj = ps;
+	localobj = null;
+	suspended = false;
+	latest_good = 0;
+	failed_attempts = 0;
+    }
+
+    synchronized public void mapHandler(Notifiable n) {
+	remoteobj = null;
+	localobj = n;
+	suspended = false;
+	latest_good = 0;
+	failed_attempts = 0;
+    }
+
+    synchronized public void suspend() {
+	suspended = true;
+    }
+
+    synchronized public void resume() {
+	suspended = false;
+    }
+
+    public int hashCode() {
+	return (identity==null) ?
+	    ((localobj == null) ? remoteobj.hashCode() : localobj.hashCode())
+	    : identity.hashCode();
+    }
+
+    public boolean isUnreachable() {
+	//
+	// the master should never be considered unreachable
+	//
+	return false;
+    }
+
+    private void handleNotifyError(Exception ex) {
+	if (failed_attempts == 0) latest_good = System.currentTimeMillis();
+	++failed_attempts;
+	if (localobj != null) {
+	    Logging.prlnerr("error (" + failed_attempts
+			    + ") notifying local client " + localobj.toString());
+	} else {
+	    Logging.prlnerr("error (" + failed_attempts
+			    + ") sending packet to " + remoteobj.toString());
+	}
+	Logging.prlnerr(ex.toString());
+    }
+
+    synchronized public boolean notify(SENPPacket pkt) {
+	if (suspended) return true;
+	try {
+	    if (localobj != null) {
+		//		localobj.notify(new Notification(pkt.event));
+		localobj.notify(pkt.event);
+	    } else {
+		remoteobj.send(pkt.buf, pkt.encode());
+	    }
+	    failed_attempts = 0;
+	    return true;
+	} catch (Exception ex) {
+	    handleNotifyError(ex);
+	    return false;
+	}
+    }
+
+    synchronized public void notify(Notification n, byte[] our_id) {
+	if (suspended) return;
+	try {
+	    if (localobj != null) {
+		localobj.notify(n);
+	    } else {
+		spkt.init();
+		spkt.id = our_id;
+		spkt.method = SENP.PUB;
+		spkt.event = n;
+		spkt.to = identity;
+		remoteobj.send(spkt.buf, spkt.encode());
+	    }
+	} catch (Exception ex) {
+	    handleNotifyError(ex);
+	}
+    }
+
+    synchronized public void notify(Notification [] s, byte[] our_id) {
+	if (suspended) return;
+	try {
+	    if (localobj != null) {
+		//
+		// here I purposely do not duplicate the sequence for
+		// efficiency reasons.  Clients should never modify
+		// objects passed through notify().
+		//
+		localobj.notify(s);
+	    } else {
+		spkt.init();
+		spkt.id = our_id;
+		spkt.method = SENP.PUB;
+		spkt.events = s;
+		spkt.to = identity;
+		remoteobj.send(spkt.buf, spkt.encode());
+	    }
+	} catch (Exception ex) {
+	    handleNotifyError(ex);
+	}
+    }
+
+    synchronized public long getMillisSinceGood() {
+	if (suspended || failed_attempts == 0) return 0;
+	return System.currentTimeMillis() - latest_good;
+    }
+
+    synchronized public short getFailedAttempts() {
+	if (suspended) return 0;
+	return failed_attempts;
+    }
+
+    public String toString() {
+	return getKey().toString();
+    }
+}
+
+class SubscriberIterator {
+    private Iterator si;
+
+    SubscriberIterator(Iterator i) {
+	si = i;
+    }
+
+    public boolean hasNext() {
+	return si.hasNext();
+    }
+
+    public Subscriber next() {
+	return (Subscriber)si.next();
+    }
+}
+
+class SSet {
+    protected Set subs;
+
+    public SSet() {
+	subs = new HashSet();
+    }
+
+    public boolean addAll(SSet s) {
+	return subs.addAll(s.subs);
+    }
+
+    public boolean add(Subscriber s) {
+	return subs.add(s);
+    }
+
+    public boolean contains(Subscriber s) {
+	return subs.contains(s);
+    }
+
+    public boolean remove(Subscriber s) {
+	return subs.remove(s);
+    }
+
+    public boolean isEmpty() {
+	return subs.isEmpty();
+    }
+
+    public SubscriberIterator iterator() {
+	return new SubscriberIterator(subs.iterator());
+    }
+}
+
+class RefSSet extends SSet {
+    public RefSSet() {
+	super();
+    }
+
+    public boolean addAll(SSet s) {
+	boolean result = false;
+	SubscriberIterator si;
+	for(si = s.iterator(); si.hasNext();) {
+	    Subscriber sub = si.next();
+	    if (subs.add(sub)) {
+		sub.addRef();
+		result = true;
+	    }
+	}
+	return result;
+    }
+
+    public boolean add(Subscriber s) {
+	if (subs.add(s)) {
+	    s.addRef();
+	    return true;
+	} else {
+	    return false;
+	}
+    }
+
+    public boolean remove(Subscriber s) {
+	if (subs.remove(s)) {
+	    s.removeRef();
+	    return true;
+	} else {
+	    return false;
+	}
+    }
+
+    protected void finalize() throws Throwable {
+	SubscriberIterator si;
+	for(si = this.iterator(); si.hasNext();)
+	    si.next().removeRef();
+    }
+}
+
+
+class Subscription {
+    public Set		preset;
+    public Set		postset;
+
+    public final Filter	filter;
+    public RefSSet subscribers;
+
+    public Subscription(Filter f) {
+	preset = new HashSet();
+	postset = new HashSet();
+	filter = new Filter(f);
+	subscribers = new RefSSet();
+    }
+
+    public Subscription(Filter f, Subscriber s) {
+	preset = new HashSet();
+	postset = new HashSet();
+	filter = new Filter(f);
+	subscribers = new RefSSet();
+	subscribers.add(s);
+    }
+
+    public String toString() {
+	Iterator i;
+	StringBuffer sb = new StringBuffer();
+	sb.append("SUB: " + Integer.toString(hashCode()));
+	sb.append("\npreset:");
+	for(i = preset.iterator(); i.hasNext();)
+	    sb.append(" "+Integer.toString(i.next().hashCode()));
+	sb.append("\npostset:");
+	for(i = postset.iterator(); i.hasNext();)
+	    sb.append(" "+Integer.toString(i.next().hashCode()));
+	sb.append("\n" + filter.toString());
+	sb.append("\nsubscribers:");
+	SubscriberIterator si;
+	for(si = subscribers.iterator(); i.hasNext();)
+	    sb.append(" "+Integer.toString(i.next().hashCode()));
+	sb.append("\n");
+	return sb.toString();
+    }
+}
+
+class Poset {
+    private Set		roots;
+
+    public Poset() {
+	roots = new HashSet();
+    }
+
+    public void clear() {
+	roots.clear();
+    }
+
+    public boolean is_root(Subscription s) {
+	return s.preset.isEmpty();
+    }
+
+    public boolean empty() {
+	return roots.isEmpty();
+    }
+
+    public Iterator rootsIterator() {
+	return roots.iterator();
+    }
+
+    synchronized public void insert(Subscription new_sub,
+				    Collection pre, Collection post) {
+	//
+	// inserts new_sub into the poset between pre and post.  the
+	// connections are rearranged in order to maintain the
+	// properties of the poset
+	//
+	Subscription x; // x always represents something in the preset
+	Subscription y; // y has to do with the postset
+	Iterator xi, yi;
+
+	if (pre.isEmpty()) {
+	    roots.add(new_sub);
+	} else {
+	    xi = pre.iterator();
+	    while(xi.hasNext()) {
+		x = (Subscription)xi.next();
+		yi = post.iterator();
+		while(yi.hasNext())
+		    disconnect(x, (Subscription)yi.next());
+		connect(x, new_sub);
+	    }
+	}
+	yi = post.iterator();
+	while(yi.hasNext()) {
+	    y = (Subscription)yi.next();
+	    connect(new_sub, y);
+	}
+    }
+
+    synchronized public void disconnect(Subscription x, Subscription y) {
+	if (x.postset.remove(y))
+	    y.preset.remove(x);
+    }
+
+    synchronized public void connect(Subscription x, Subscription y) {
+	if (x.postset.add(y))
+	    y.preset.add(x);
+    }
+
+    synchronized public Set remove(Subscription s) {
+	//
+	// removes s from the poset returning the set of root
+	// subscription uncovered by s
+	//
+	Set result = new HashSet();
+	Subscription x, y;
+	Iterator xi, yi;
+	//
+	// 1. disconnect s from every successor of s but maintains s.postset
+	//
+	yi = s.postset.iterator();
+	while(yi.hasNext()) {
+	    y = (Subscription)yi.next();
+	    y.preset.remove(s);
+	}
+
+	if (s.preset.isEmpty()) {
+	    //
+	    // 2.1 if s is a root subscription, adds as a root every
+	    // successor that remains with an empty preset, i.e.,
+	    // every subscription that was a successor of s and that
+	    // is now a root subscription...
+	    //
+	    yi = s.postset.iterator();
+	    while(yi.hasNext()) {
+		y = (Subscription)yi.next();
+		if (y.preset.isEmpty()) {
+		    roots.add(y);
+		    result.add(y);
+		}
+	    }
+	    roots.remove(s);
+	} else {
+	    //
+	    // 2.2 disconnects every predecessor of s thereby reconnecting
+	    // predecessors to successors. A predecessor X is re-connected
+	    // to a successor Y only if X does not have an immediate
+	    // successor X' that covers Y (see is_indirect_successor).
+	    //
+	    xi = s.preset.iterator();
+	    while(xi.hasNext()) {
+		x = (Subscription)xi.next();
+		x.postset.remove(s);
+		yi = s.postset.iterator();
+		while(yi.hasNext()) {
+		    y = (Subscription)yi.next();
+		    if (!is_indirect_successor(x, y))
+			connect(x, y);
+		}
+	    }
+	}
+	return result;
+    }
+
+    synchronized public boolean is_indirect_successor(Subscription x,
+						      Subscription y) {
+	//
+	// says whether x indirectly covers y.
+	//
+	Iterator i = x.postset.iterator();
+	while(i.hasNext())
+	    if (Covering.covers(((Subscription)i.next()).filter, y.filter))
+		return true;
+	return false;
+    }
+
+    synchronized public Set predecessors(Filter f, Subscriber s) {
+	//
+	// computes the set of immediate predecessors of filter f that
+	// do not contain subscriber s.  If the poset contains any
+	// subscription covering f that already contains s, then this
+	// function returns null.  Otherwise, it returns the
+	// collection of predecessors of f.  If the poset contains a
+	// filter f'=f, the result will be a set containing that
+	// (only) subscription.
+	//
+	LinkedList to_visit = new LinkedList();
+	Set visited = new HashSet();
+	Subscription sub, y;
+	Iterator i = roots.iterator();
+	boolean found_lower;
+
+	while(i.hasNext()) {
+	    sub = (Subscription)i.next();
+	    if (Covering.covers(sub.filter, f)) {
+		if (sub.subscribers.contains(s)) {
+		    return null;
+		} else {
+		    to_visit.addLast(sub);
+		}
+	    }
+	}
+	Set result = new HashSet();
+
+	ListIterator li;
+	while((li = to_visit.listIterator()).hasNext()) {
+	    sub = (Subscription)li.next();
+	    li.remove();
+	    i = sub.postset.iterator();
+	    found_lower = false;
+	    while(i.hasNext()) {
+		y = (Subscription)i.next();
+		if (visited.add(y)) {
+		    if (Covering.covers(y.filter, f)) {
+			found_lower = true;
+			if (sub.subscribers.contains(s)) {
+			    return null;
+			} else {
+			    to_visit.addLast(y);
+			}
+		    }
+		} else if (!found_lower) {
+		    if (Covering.covers(y.filter, f))
+			found_lower = true;
+		}
+	    }
+	    if(!found_lower) result.add(sub);
+	}
+	return result;
+    }
+
+    synchronized public SSet matchingSubscribers(Notification e) {
+	//
+	// computes the set of subscribers that are interested in e.
+	// This includes the subscribers of all the subscriptions in
+	// the poset that match e
+	//
+	SSet result = new SSet();
+	Iterator i = roots.iterator();
+	LinkedList to_visit = new LinkedList();
+	Set visited = new HashSet();
+	Subscription sub;
+
+	while(i.hasNext()) {
+	    sub = (Subscription)i.next();
+	    if (Covering.apply(sub.filter, e)) {
+		to_visit.addLast(sub);
+		result.addAll(sub.subscribers);
+	    }
+	}
+
+	ListIterator li;
+	while((li = to_visit.listIterator()).hasNext()) {
+	    sub = (Subscription)li.next();
+	    li.remove();
+	    i = sub.postset.iterator();
+	    while(i.hasNext()) {
+		Subscription y = (Subscription)i.next();
+		if (visited.add(y) && Covering.apply(y.filter, e)) {
+		    to_visit.addLast(y);
+		    result.addAll(y.subscribers);
+		}
+	    }
+	}
+	return result;
+    }
+
+    synchronized public Set successors(Filter f, Collection pred) {
+	//
+	// given a filter f and a set pred, the set of the immediate
+	// predecessors of f in the poset, computes the set of
+	// immediate successors of f in the poset.
+	//
+	// Idea: I must walk through the sub-poset of of this poset
+	// that is covered by the set of predecessors (the whole poset
+	// if there are no predecessors), looking for filters f1, f2,
+	// ..., fn that are covered by f.  I do that by using a queue
+	// of filters to_visit.
+	//
+	// to_visit is initialized with the element of pred or with
+	// the root filters. For every f' in to_visit, if f' is covered
+	// by f then I remove f' from to_visit and I add it to the
+	// result.  If f' is not covered by f, then I add all its
+	// successors that I haven't visited to to_visit.
+	//
+	LinkedList to_visit = new LinkedList();
+	Set visited = new HashSet();
+	Subscription sub, y;
+	Iterator i;
+	//
+	// initialize to_visit
+	//
+	if (pred == null || pred.isEmpty()) {
+	    to_visit.addAll(roots);
+	} else {
+	    i = pred.iterator();
+	    while(i.hasNext()) {
+		to_visit.addLast((Subscription)i.next());
+	    }
+	}
+	visited.addAll(to_visit);
+	Set result = new HashSet();
+	ListIterator li;
+	while((li = to_visit.listIterator()).hasNext()) {
+	    sub = (Subscription)li.next();
+	    li.remove();
+	    if (Covering.covers(f, sub.filter)) {
+		result.add(sub);
+	    } else {
+		i = sub.postset.iterator();
+		while(i.hasNext()) {
+		    y = (Subscription)i.next();
+		    if (visited.add(y)) {
+			to_visit.addLast(y);
+		    }
+		}
+	    }
+	}
+
+	return result;
+    }
+
+    synchronized public Subscription insert_subscription(Filter f,
+							 Subscriber s) {
+	//
+	// inserts a subscription in the poset
+	//
+	Set pred = predecessors(f,s);
+	Subscription sub;
+	if (pred == null) return null;
+	if (pred.size() == 1) {
+	    sub = (Subscription)pred.iterator().next();
+	    if (Covering.covers(f,sub.filter)) {
+		//
+		// pred contains exactly f, so simply add s to the set
+		// of subscribers
+		//
+		sub.subscribers.add(s);
+		clear_subposet(sub, s);
+		return sub;
+	    }
+	}
+	sub = new Subscription(f, s);
+	insert(sub, pred, successors(f, pred));
+	clear_subposet(sub, s);
+	return sub;
+    }
+
+    synchronized public void clear_subposet(Subscription start, Subscriber s) {
+	//
+	// removes subscriber s from all the subscriptions covered by
+	// start, excluding start itself.  This also removes
+	// subscriptions that remain with no subscribers.
+	//
+	LinkedList to_visit = new LinkedList();
+	Set visited = new HashSet();
+
+	to_visit.addAll(start.postset);
+
+	ListIterator li;
+	while((li = to_visit.listIterator()).hasNext()) {
+	    Subscription sub = (Subscription)li.next();
+	    li.remove();
+	    if (visited.add(sub)) {
+		if (sub.subscribers.remove(s)) {
+		    if (sub.subscribers.isEmpty()) remove(sub);
+		} else {
+		    to_visit.addAll(sub.postset);
+		}
+	    }
+	}
+    }
+
+    synchronized public Set to_remove(Filter f, Subscriber s) {
+	//
+	// removes subscriber s from the subscriptions covered by f.
+	// If f==null, it removes s from all the subscriptions in the
+	// poset. Returns the set of empty subscriptions, i.e., those
+	// that remain with no subscribers.
+	//
+	Set result = new HashSet();
+	LinkedList to_visit = new LinkedList();
+	Subscription sub;
+
+	if (f == null) {
+	    //
+	    // f==null ==> universal filter (same thing as BYE)
+	    // so my starting point is the set of root subscriptions
+	    //
+	    to_visit.addAll(roots);
+	} else {
+	    Set pred = predecessors(f, s);
+	    if (pred != null && pred.size() == 1) {
+		sub = (Subscription)pred.iterator().next();
+		if (Covering.covers(f,sub.filter)) {
+		    //
+		    // pred contains exactly f, so remove s and see if
+		    // f remains with no subscribers
+		    //
+		    if (sub.subscribers.remove(s)) {
+			if (sub.subscribers.isEmpty())
+			    result.add(sub);
+		    }
+
+		    return result;
+		}
+	    }
+	    to_visit.addAll(successors(f, pred));
+	}
+
+	Set visited = new HashSet();
+	ListIterator li;
+
+	while((li = to_visit.listIterator()).hasNext()) {
+	    sub = (Subscription)li.next();
+	    li.remove();
+	    if (visited.add(sub)) {
+		if (sub.subscribers.remove(s)) {
+		    if (sub.subscribers.isEmpty())
+			result.add(sub);
+		} else {
+		    to_visit.addAll(sub.postset);
+		}
+	    }
+	}
+	return result;
+    }
+
+    synchronized public void print(PrintStream out) {
+	//
+	// prints the poset
+	//
+	LinkedList to_visit = new LinkedList();
+	Set visited = new HashSet();
+
+	to_visit.addAll(roots);
+
+	ListIterator li;
+	while((li = to_visit.listIterator()).hasNext()) {
+	    Subscription sub = (Subscription)li.next();
+	    li.remove();
+	    if (visited.add(sub)) {
+		out.println(sub.toString());
+		to_visit.addAll(sub.postset);
+	    }
+	}
+    }
+}
+
+class Publication {
+    public SENPPacket		packet;
+    public PacketNotifiable	handler;
+    public Publication		next;
+
+    public Publication(SENPPacket p, PacketNotifiable s) {
+	packet = p;
+	handler = s;
+    }
+}
+
+class EmptyPatternException extends SienaException {
+    // ...work in progress...
+}
+
+//
+// we implement pattern recognition with PatternMatcher.  A
+// PatternMatcher is a ``parser'' of event sequences.  A
+// PatternMatcher receives notifications (single events) from Siena
+// and holds a list of subscribers.  When a PatternMatcher recognizes
+// a pattern, it notifies the list of subscribers passing the matching
+// sequence of events.
+//
+class PatternMatcher implements Notifiable {
+    public RefSSet		subscribers	= null;
+    public Pattern		pattern		= null;
+    private LinkedList		notifications	= null;
+    private byte[]		our_id;
+
+    public PatternMatcher(Pattern p, byte[] id) {
+	pattern = new Pattern(p);
+	notifications = new LinkedList();
+	subscribers = new RefSSet();
+	our_id = id;
+    }
+
+    public void notify(Notification s[]) {}
+
+    public void notify(Notification n) {
+	//
+	// This method receives the elementary components of a pattern
+	// and does the matching.
+	//
+	// WARNING: this is my own naive matching algorithm.  It is
+	// certainly not optimized and it might not even be correct!
+	// I should look up Knuth-Morris-Pratt matching algorithm,
+	// but I have no time now...
+	//
+	// ...work in progress... bigtime.
+	//
+	int curr = notifications.size();
+	notifications.addLast(new Notification(n));
+	//
+	// pattern.length must be > 0 (see constructor)
+	//
+	if (Covering.apply(pattern.filters[curr], n)) {
+	    if (++curr == pattern.filters.length) {
+		//
+		// MATCHED!  builds the array of notifications,
+		//
+		Notification sequence[] = new Notification[curr];
+		while(--curr >= 0)
+		    sequence[curr] = (Notification)notifications.removeLast();
+		//
+		// notify subscribers
+		//
+		SubscriberIterator i = subscribers.iterator();
+		while(i.hasNext())
+		    i.next().notify(sequence, our_id);
+
+	    }
+	} else {
+	    //
+	    // no match here, I've got to backtrack...  try to cut the
+	    // head of the queue of notifications and match a
+	    // shorter (most recent) prefix of filters.
+	    //
+	    for(notifications.removeFirst(); !notifications.isEmpty();
+		notifications.removeFirst()) {
+		ListIterator li = notifications.listIterator();
+		for(curr = 0; li.hasNext(); ++curr)
+		    if (!Covering.apply(pattern.filters[curr],
+					(Notification)li.next())) break;
+		if (!li.hasNext()) {
+		    //
+		    // found a shorter prefix
+		    //
+		    return;
+		}
+	    }
+	}
+    }
+}
+
+/** implementation of a Siena event notification service.
+ *
+ *  This is the primary implementation of the Siena event notification
+ *  service. A <code>HierarchicalDispatcher</code> can serve as a
+ *  Siena event service for local (same Java VM) clients as well as
+ *  remote clients.  <code>HierarchicalDispatcher</code>s can also be
+ *  combined in a distributed architecture with other dispatchers.
+ *  Every dispatcher can be connected to a <em>master</em> dispatcher,
+ *  thereby forming a hierarchical structure.  The hierarchy of
+ *  dispatchers is assembled incrementally as new dispatchers are
+ *  created and connected to a master that already belongs to the
+ *  hierarchy.
+ *
+ *  <p>A <code>HierarchicalDispatcher</code> uses a {@link
+ *  PacketReceiver} to receive notifications, subscriptions and
+ *  unsubscriptions from external clients and from its <em>master</em>
+ *  dispatcher.  In order to receive and process external requests, a
+ *  <code>HierarchicalDispatcher</code> can either use a pool of
+ *  internal threads, or it can use users' threads.  See {@link
+ *  #DefaultThreadCount}, {@link #setReceiver(PacketReceiver)}, and
+ *  {@link #setReceiver(PacketReceiver, int)}
+ *
+ *  @see Siena
+ *  @see ThinClient
+ **/
+public class HierarchicalDispatcher implements Siena,  Runnable {
+    private Poset			subscriptions	= new Poset();
+    private Map				contacts	= new HashMap();
+    private byte[]			master_id	= null;
+    private byte[]			master_handler	= null;
+    private PacketSender		master		= null;
+    private PacketReceiver		listener	= null;
+    private byte[]			my_identity	= null;
+    private List			matchers	= new LinkedList();
+
+    private SENPPacket			spkt = new SENPPacket();
+    private byte []			sndbuf = new byte[SENP.MaxPacketLen];
+
+    private PacketSenderFactory		sender_factory;
+    static private PacketSenderFactory	default_sender_factory
+					  = new GenericSenderFactory();
+
+    /** sets the packet-sender factory associated with this
+     *  HierarchicalDispatcher
+     *
+     *  @see #setDefaultPacketSenderFactory(PacketSenderFactory)
+     **/
+    public void setPacketSenderFactory(PacketSenderFactory f) {
+	sender_factory = f;
+    }
+
+    /** default packet-sender factory for HierarchicalDispatcher
+     *  interfaces
+     *
+     *  every new HierarchicalDispatcher objects is assigned this
+     *  factory
+     *
+     *  @see #setPacketSenderFactory(PacketSenderFactory)
+     **/
+    static public void setDefaultPacketSenderFactory(PacketSenderFactory f) {
+	default_sender_factory = f;
+    }
+
+    /** default number of threads handling external requests.
+     *
+     *  Every HierarchicalDispatcher creates a pool of threads to read
+     *  and process incoming requests.  This parameter determines the
+     *  default number of threads in the pool.  The initial default
+     *  value is 5. See {@link #setReceiver(PacketReceiver, int)} for
+     *  the semantics of this value.  <code>DefaultThreadCount</code>
+     *  is used to create threads upon call to {@link
+     *  #setReceiver(PacketReceiver)}.
+     *
+     *	@see #setReceiver(PacketReceiver)
+     *	@see #setReceiver(PacketReceiver,int)
+     *	@see #setMaster(String)
+     **/
+    public  int			DefaultThreadCount = 5;
+
+    /** number of failed notifications before a subscriber is
+     *	implicitly disconnected.
+     *
+     *	The default value of <code>MaxFailedConnectionNumber</code> is 2.
+     *
+     *  HierachicalDispatcher implements a garbage collection
+     *  mechanism for unreachable subscribers.  This mechanism
+     *  implicitly unsubscribes a client when the dispatcher fails to
+     *  connect to the client for a given number of times and after a
+     *  given number of milliseconds.  More formally, the dispatcher
+     *  considers the sequence of consecutive failed connections not
+     *  followed by any successful connection.  This sequence is
+     *  charachterized by two parameters: its <em>length</em> and its
+     *  <em>duration</em>.<p>
+     *
+     *	<code>MaxFailedConnectionsNumber</code> represents the upper
+     *	bound to the length of the sequence, while
+     *	<code>MaxFailedConnectionsDuration</code> represents the upper
+     *	bound to the duration of the sequence.  For both parameters, a
+     *	negative value means
+     *	<em>infinity</em>. <code>removeUnreachableSubscriber()</code>
+     *	removes all the subscriptions of those subscribers that have
+     *	not been reachable for more than
+     *	<code>MaxFailedConnectionsNumber</code> times and more than
+     *	<code>MaxFailedConnectionsDuration</code> milliseconds.
+     *	Formally, a subscriber that has not been reachable for
+     *	<em>T</em> milliseconds for <em>N</em> notifications will be
+     *	removed according to the following conditions:
+     *
+     *	if (MaxFailedConnectionsNumber &gt;= 0 &amp;&amp; MaxFailedConnectionsDuration &gt;= 0) {
+     *      if (T &gt; MaxFailedConnectionsDuration &amp;&amp; N &gt; MaxFailedConnectionsNumber)
+     *          removeit!
+     *	} else if (MaxFailedConnectionsNumber &gt;= 0) {
+     *      if (N &gt; MaxFailedConnectionsNumber)
+     *          remove it!
+     *	} else if (MaxFailedConnectionsDuration &gt;= 0) {
+     *	    if (T &gt; MaxFailedConnectionsDuration)
+     *		remove it!
+     *	}
+     *
+     *	@see #MaxFailedConnectionsDuration
+     **/
+    public  int			MaxFailedConnectionsNumber	= 2;
+
+    /** milliseconds before automatic unsubscription is activated.
+     *
+     *  The default value of <code>MaxFailedConnectionsDuration</code>
+     *  is 5000 (i.e., 5 seconds).
+     *
+     *  @see #MaxFailedConnectionsNumber
+     **/
+    public  long		MaxFailedConnectionsDuration	= 5000;
+
+    /** creates a dispatcher with a specific <em>identity</em>.
+     *
+     *	Every object involved in Siena communications must have a
+     *	unique identity.  Therefore the scope of the identity of this
+     *	dispatcher is the entire event notifications service.
+     *
+     *	@param id identity given to the dispatcher.  It is crucial
+     *	          that the identity of a dispatcher is unique over the
+     *	          whole event notification service.
+     **/
+    public HierarchicalDispatcher(String id) {
+	my_identity = id.getBytes();
+	sender_factory = default_sender_factory;
+	Monitor.add_node(my_identity, Monitor.SienaNode);
+    }
+
+    /** creates a dispatcher with a randomly-generated
+     *	(probabilistically unique) <em>identity</em>.
+     **/
+    public HierarchicalDispatcher() {
+	my_identity = SienaId.getId().getBytes();
+	sender_factory = default_sender_factory;
+	Monitor.add_node(my_identity, Monitor.SienaNode);
+    }
+
+
+    /** process a single request, using the caller's thread.
+     *
+     *  The default value of <code>MaxFailedConnectionsDuration</code>
+     *  is 5000 (i.e., 5 seconds).
+     *
+     *  @see #DefaultThreadCount
+     *  @see #setReceiver(PacketReceiver)
+     *  @see #setReceiver(PacketReceiver, int)
+     **/
+    public void processOneRequest() throws SienaException {
+	SENPPacket req = SENPPacket.allocate();
+	try {
+	    int res;
+	    req.init();
+	    res = listener.receive(req.buf);
+	    Logging.prlnlog(new String(req.buf, 0, res));
+	    req.init(res);
+	    req.decode();
+	    if (req != null) processRequest(req);
+	} finally {
+	    SENPPacket.recycle(req);
+	}
+    }
+
+    public void run() {
+	SENPPacket req = SENPPacket.allocate();
+	int res;
+	while(true) {
+	    try {
+		res = listener.receive(req.buf);
+		req.init(res);
+		req.decode();
+		processRequest(req);
+	    } catch (SENPInvalidFormat ex) {
+		Logging.prlnerr("invalid request: " + ex.toString());
+	    } catch (PacketReceiverClosed ex) {
+		if (ex.getIOException() != null)
+		    Logging.prlnerr("error in packet receiver: "
+				    + ex.toString());
+		SENPPacket.recycle(req);
+		return;
+	    } catch (PacketReceiverFatalError ex) {
+		Logging.prlnerr("fatal error in packet receiver: "
+				+ ex.toString());
+		SENPPacket.recycle(req);
+		return;
+	    } catch (PacketReceiverException ex) {
+		Logging.prlnerr("non-fatal error in packet receiver: "
+				+ ex.toString());
+	    }
+	}
+    }
+
+    synchronized void removeUnreachableSubscriber(Subscriber sub) {
+	if (MaxFailedConnectionsNumber < 0 && MaxFailedConnectionsDuration < 0)
+	    return;
+
+	boolean to_remove = false;
+	if (MaxFailedConnectionsNumber >= 0
+	    && MaxFailedConnectionsDuration >= 0) {
+	    //
+	    // pay attention to both time and count (conjunction)
+	    //
+	    if (sub.getMillisSinceGood() > MaxFailedConnectionsDuration
+		&& sub.getFailedAttempts() > MaxFailedConnectionsNumber)
+		to_remove = true;
+	} else if (MaxFailedConnectionsNumber >= 0) {
+	    if (sub.getFailedAttempts() > MaxFailedConnectionsNumber)
+		to_remove = true;
+	} else if (MaxFailedConnectionsDuration >= 0) {
+	    if (sub.getMillisSinceGood() > MaxFailedConnectionsDuration)
+		to_remove = true;
+	}
+	if (to_remove) {
+	    Logging.prlnlog("removing unreachable subscriber "
+			    + sub.toString());
+	    unsubscribe(null, sub, null);
+	}
+    }
+
+    /** sets the <em>packet receiver</em> for this server.
+     *
+     *	A <em>packet receiver</em> accepts notifications,
+     *	subscriptions, and other requests on some communication
+     *	channel.  <code>setReceiver</code> will shut down any
+     *	previously activated receiver for this dispatcher.  This
+     *	method does not guarantee a transactional switch to a new
+     *	receiver.  This means that some requests might get lost while
+     *	the server has closed the old port and before it reopens the
+     *	new port.
+     *
+     *  <p>This method simply calls {@link #setReceiver(PacketReceiver,
+     *  int)} using {@link #DefaultThreadCount} as a default value.
+     *
+     *	@param r is the receiver
+     *
+     *  @see #shutdown()
+     *  @see #setReceiver(PacketReceiver, int)
+     **/
+    public void setReceiver(PacketReceiver r) {
+	setReceiver(r, DefaultThreadCount);
+    }
+
+    /** sets the <em>packet receiver</em> for this server.
+     *
+     *	A <em>packet receiver</em> accepts notifications,
+     *	subscriptions, and other requests on some communication
+     *	channel.  <code>setReceiver</code> will shut down any
+     *	previously activated receiver for this dispatcher.  This
+     *	method does not guarantee a transactional switch to a new
+     *	receiver.  This means that some requests might get lost while
+     *	the server has closed the old port and before it reopens the
+     *	new port.
+     *
+     *	@param r the packet receiver
+     *  @param threads is the number of threads associated with the
+     *  receiver, and therefore to the whole server.  A positive value
+     *  causes this dispatcher to create threads.  A value of 0 causes
+     *  the dispatcher not to create any thread, In this case, the
+     *  application must explicitly call {@link #processOneRequest()}.
+     *
+     *  @see #shutdown()
+     *  @see #setMaster(String)
+     **/
+    synchronized public void setReceiver(PacketReceiver r, int threads) {
+	if (listener != null) {
+	    try {
+		listener.shutdown();
+	    } catch (PacketReceiverException ex) {
+		Logging.exerr(ex);
+		//
+		// ...work in progress...
+		//
+	    }
+	    //
+	    // this should send a PacketReceiverClosed exception to
+	    // every thread that is waiting for packets on the old
+	    // listener, which will make them exit normally.  However,
+	    // because of bugs in the JVM, or because of bad
+	    // implementations of packetReceiver, this might not be
+	    // true.  ...work in progress...
+	    //
+	}
+	listener = r;
+
+	if (master != null) {
+	    spkt.init();
+	    spkt.method = SENP.MAP;
+	    spkt.id = my_identity;
+	    spkt.handler = listener.uri();
+	    try {
+		master.send(spkt.buf, spkt.encode());
+	    } catch (Exception ex) {
+		Logging.prlnerr("error sending packet to "
+				+ master.toString());
+		Logging.exerr(ex);
+		//
+		// I should really do something here
+		// ...work in progress...
+		//
+	    }
+	}
+	//
+	// now fires off the reader threads for this dispatcher
+	//
+	while (threads-- > 0) {
+	    Thread t = new Thread(this);
+	    //
+	    // Perhaps I should set t as a deamon thread...
+	    //
+	    t.start();
+	}
+    }
+
+    /** creates a TCP-based <em>packet receiver</em> for this server.
+     *
+     *	@deprecated as of Siena 1.1.0, replaced by {@link
+     *              #setReceiver(PacketReceiver) setReceiver()}
+     *
+     *  @param port is the port number allocated by the listener,
+     *	            <code>0</code> allocates a random available port
+     *	            number, the default value for Siena is
+     *	            <code>SENP.DEFAULT_PORT</code>
+     *
+     *  @exception IOException when the given port is already in use
+     **/
+    synchronized public void setListener(int port) throws IOException {
+	setReceiver(new TCPPacketReceiver(port));
+    }
+
+
+    /** connects this dispatcher to a <em>master</em> dispatcher.
+     *
+     *	If this dispatcher is already connected to a master
+     *	dispatcher, <code>setMaster</code> disconnects the old one and
+     *	connects the new one, thereby unsubscribing all the top-level
+     *	subscriptions and resubscribing with the new one.  This method
+     *	should be used only when this dispatcher <em>needs</em> to
+     *	switch to a different master, it is not necessary (it is in
+     *	fact very inefficient) to set the master before every
+     *	subscription or notification.
+     *
+     *	<p>This method does not guarantee a transactional switch.  This
+     *	means that some notifications might be lost when the server
+     *	has detached from the old master and before it re-subscribes
+     *	with the new master.
+     *
+     *	<p>If this dispatcher does not have a <em>packet receiver</em>
+     *	associated with it, <code>setMaster</code> implicitly sets up
+     *	one for the dispatcher.  The default receiver is a
+     *	<code>TCPPacketReceiver</code> listening to a randomly
+     *	allocated port.  If you are not happy with the default
+     *	decision, you should call <code>setReceiver()</code> before
+     *	you call <code>setMaster</code>.
+     *
+     *	@param uri is the external identifier of the master dispatcher
+     *	           (e.g., * <code>"senp://host.domain.edu:8765"</code>)
+     *  @see #setReceiver(PacketReceiver)
+     *  @see #shutdown()
+     **/
+    synchronized public void setMaster(String uri)
+	throws InvalidSenderException, java.io.IOException {
+
+	PacketSender new_master = sender_factory.createPacketSender(uri);
+
+	disconnectMaster();
+	boolean new_listener = false;
+	if (listener == null) {
+	    setReceiver(new TCPPacketReceiver(0));
+	    new_listener = true;
+	}
+	master_handler = uri.getBytes();
+	master = new_master;
+	//
+	// sends a WHO packet to figure out the identity of the master
+	// server.  This dispatcher uses the "to" field of the SENP
+	// packet to tell the master server the handler used by this
+	// server to reach the master server.  (see reply_who())
+	//
+	try {
+	    spkt.init();
+	    spkt.method = SENP.WHO;
+	    spkt.ttl = 2;			// round-trip
+	    spkt.to = master_handler;
+	    spkt.id = my_identity;
+	    spkt.handler = listener.uri();
+	    master.send(spkt.buf, spkt.encode());
+	    //
+	    // perhaps I should sit here waiting for the INF response
+	    // of the server
+	    //
+	    // ...to be continued...
+	    //
+	} catch (Exception ex) {
+	    Logging.prlnerr("error sending packet to " + master.toString());
+	    Logging.exerr(ex);
+	    master = null;
+	    master_handler = null;
+	    if (new_listener) {
+		try {
+		    listener.shutdown();
+		} catch (PacketReceiverException pex) {
+		    Logging.exerr(pex);
+		}
+	    }
+	    //
+	    // of course I should do something here...
+	    // ...work in progress...
+	    //
+	}
+	//
+	// sends all the top-level subscriptions to the new master
+	//
+	for(Iterator i = subscriptions.rootsIterator(); i.hasNext();) {
+	    Subscription s = (Subscription)i.next();
+	    try {
+		spkt.init();
+		spkt.method = SENP.SUB;
+		spkt.ttl = SENP.DefaultTtl;
+		spkt.id = my_identity;
+		spkt.handler = listener.uri();
+		spkt.filter = s.filter;
+		master.send(spkt.buf, spkt.encode());
+	    } catch (Exception ex) {
+		Logging.prlnerr("error sending packet to " + master.toString());
+		Logging.exerr(ex);
+		//
+		// of course I should do something here...
+		// ...work in progress...
+		//
+	    }
+	}
+    }
+
+    /** suspends the connection with the <em>master</em> server of
+     *	this dispatcher.
+     *
+     *	This causes the <em>master</em> server to stop sending
+     *	notification to this dispatcher.  The master correctly
+     *	maintains all the existing subscriptions so that the flow of
+     *	notification can be later resumed (see {@link #resumeMaster()
+     *	resumeMaster}.  This operation can be used when this
+     *	dispatcher, that is this virtual machine, is going to be
+     *	temporarily disconnected from the network or somehow
+     *	unreachable from its master server.
+     *
+     *	@see #resumeMaster()
+     **/
+    synchronized public void suspendMaster() {
+	try {
+	    spkt.init();
+	    spkt.method = SENP.SUS;
+	    spkt.to = master_handler;
+	    spkt.id = my_identity;
+	    spkt.handler = listener.uri();
+	    master.send(spkt.buf, spkt.encode());
+	} catch (Exception ex) {
+	    Logging.prlnerr("error sending packet to " + master.toString());
+	    Logging.exerr(ex);
+	    //
+	    // of course I should do something here...
+	    // ...work in progress...
+	    //
+	}
+    }
+
+    /** resumes the connection with the <em>master</em> server.
+     *
+     *	This causes the <em>master</em> server to resume sending
+     *	notification to this dispatcher.
+     *
+     *	@see #suspendMaster()
+     **/
+    synchronized public void resumeMaster() {
+	try {
+	    spkt.init();
+	    spkt.method = SENP.RES;
+	    spkt.to = master_handler;
+	    spkt.id = my_identity;
+	    spkt.handler = listener.uri();
+	    master.send(spkt.buf, spkt.encode());
+	} catch (Exception ex) {
+	    Logging.prlnerr("error sending packet to " + master.toString());
+	    Logging.exerr(ex);
+	    //
+	    // of course I should do something here...
+	    // ...work in progress...
+	    //
+	}
+    }
+
+    /** returns the identity of this dispatcher.
+     *
+     *  every object in a Siena network has a unique identifier.  This
+     *  method returns the identifier of this dispatcher.
+     *
+     *  @see #HierarchicalDispatcher(String)
+     **/
+    synchronized public String getIdentity() {
+	return new String(my_identity);
+    }
+
+    /** returns the <em>uri</em> of the master server associated
+     *	with this dispatcher.
+     *
+     *  @return URI of the master server or <code>null</code> if the
+     *		master server is not set
+     *
+     *  @see #setMaster(String)
+     **/
+    synchronized public String getMaster() {
+	if (master_handler == null) return null;
+	return new String(master_handler);
+    }
+
+    /** returns the listener associated with this dispatcher.
+     *
+     *	@return receiver of this dispatcher (possibly null)
+     *
+     *  @see #setReceiver(PacketReceiver)
+     **/
+    synchronized public PacketReceiver getReceiver() {
+	return listener;
+    }
+
+    synchronized private void disconnectMaster() {
+	if (master != null) {
+	    try {
+		spkt.init();
+		spkt.method = SENP.BYE;
+		spkt.id = my_identity;
+		spkt.to = master_handler;
+		master.send(spkt.buf, spkt.encode());
+	    } catch (PacketSenderException ex) {
+		Logging.prlnerr("error sending packet to "
+				+ master.toString() + ": " + ex.toString());
+		//
+		// well, what would you do in this case?
+		// ...work in progress...
+		//
+	    }
+	    master = null;
+	    master_handler = null;
+	}
+    }
+
+    private void processRequest(SENPPacket req) {
+	Logging.prlnlog("processRequest: " + req);
+	if (req == null) {
+	    Logging.prlnerr("processRequest: null request");
+	    return;
+	}
+
+	if (req.ttl <= 0) return;
+	req.ttl--;
+	try {
+	    switch(req.method) {
+	    case SENP.PUB: publish(req); break;
+	    case SENP.SUB: subscribe(req); break;
+	    case SENP.BYE: req.pattern = null; req.filter = null;
+	    case SENP.UNS: unsubscribe(req); break;
+	    case SENP.WHO: reply_who(req); break;
+	    case SENP.INF: get_info(req); break;
+	    case SENP.SUS: suspend(req); break;
+	    case SENP.RES: resume(req); break;
+	    case SENP.MAP: map(req); break;
+	    case SENP.CNF: configure(req); break;
+	    case SENP.OFF:
+		shutdown();
+		//
+		// BEGIN_UNOFFICIAL_PATCH
+		try { Thread.sleep(500); } catch (Exception ex) {};
+		System.exit(0);
+		// END_UNOFFICIAL_PATCH
+		//
+		break;
+	    case SENP.NOP: break;
+	    default:
+		Logging.prlnerr("processRequest: unknown method: " + req);
+		//
+		// can't handle this request (yet)
+		// ...work in progress...
+		//
+	    }
+	} catch (Exception ex) {
+	    Logging.exerr(ex);
+	    //
+	    // log something here ...work in progress...
+	    //
+	}
+    }
+
+    synchronized private void configure(SENPPacket req) {
+	if (req.ttl == 0) return;
+	if (req.handler == null) {
+	    Logging.prlnlog("reconfigure: disconnecting form master");
+	    disconnectMaster();
+	} else {
+	    try {
+		String new_master = new String(req.handler);
+		Logging.prlnlog("reconfigure: switching to master "
+				+ new_master);
+		setMaster(new_master);
+	    } catch (Exception ex) {
+		Logging.prlnerr("configure: failed reconfiguration request: "
+				+ ex.toString());
+	    }
+	}
+    }
+
+    synchronized private void map(SENPPacket req) {
+	if (req.id == null || req.ttl == 0 || req.handler == null) return;
+	Subscriber s = (Subscriber)contacts.get(new String(req.id));
+	if (s != null)
+	    try {
+		s.mapHandler(sender_factory.createPacketSender(new String(req.handler)));
+	    } catch (InvalidSenderException ex) {
+		Logging.prlnerr("error while mapping handler "
+				+ new String(req.handler) + ": " + ex);
+		Logging.prlnerr("client not remapped");
+	    }
+    }
+
+    synchronized private void suspend(SENPPacket req) {
+	if (req.id == null || req.ttl == 0) return;
+	Subscriber s = (Subscriber)contacts.get(new String(req.id));
+	if (s != null) s.suspend();
+    }
+
+    synchronized private void resume(SENPPacket req) {
+	if (req.id == null || req.ttl == 0) return;
+	Subscriber s = (Subscriber)contacts.get(new String(req.id));
+	if (s != null) s.resume();
+    }
+
+    private void reply_who(SENPPacket req) {
+	if (req.handler == null || req.ttl == 0) return;
+	try {
+	    PacketSender ps;
+	    ps = sender_factory.createPacketSender(new String(req.handler));
+
+	    req.method = SENP.INF;
+	    req.id = my_identity;
+	    //
+	    // it is important that this dispatcher returns the
+	    // handler used by the client, as opposed to this
+	    // listener's handler, so that the client can match
+	    // handler and identity.
+	    //
+	    req.handler = req.to;
+	    req.to = null;
+	    ps.send(req.buf, req.encode());
+	} catch (Exception ex) {
+	    Logging.prlnerr("can't contact handler: "
+			    + new String(req.handler) + ": " + ex.toString());
+	}
+    }
+
+    synchronized private void get_info(SENPPacket req) {
+	if (req.handler == null || req.id == null) return;
+	//
+	// right now, the dispatcher uses INF (info) packets (only) to
+	// figure out the identity of its master server...  ...so this
+	// test is not strictly necessary.  It's here to prevent
+	// spurious (or malicious) INF packets from screwing up this
+	// dispatcher
+	//
+	if (SENP.match(req.handler, master_handler)) {
+	    master_id = req.id;
+	    Monitor.connect(my_identity, master_id);
+	} else {
+	    Logging.prlnerr("Warning: unknown handler in INF message: "
+			    + req + "\nexpecting: `"+master_handler+"'");
+	}
+    }
+
+    private void publish(SENPPacket req) {
+	if (req.event == null) {
+	    Logging.prlnerr("Warning: null event in PUB message: "+req);
+	    return;
+	}
+	byte[] sender = req.id;
+	req.id = my_identity;
+	if (sender != null) Monitor.notify(sender, my_identity);
+	//
+	// first forward to master
+	//
+	if (master != null  && req.ttl > 0 &&
+	    (sender == null || !SENP.match(sender, master_id))) {
+		req.to = (master_id != null) ? master_id : master_handler;
+		try {
+		    master.send(req.buf, req.encode());
+		} catch (Exception ex) {
+		    Logging.prlnerr("error sending packet to master "
+				    + master.toString());
+		    Logging.prlnerr(ex.toString());
+		}
+	    }
+	//
+	// then find all the interested subscribers
+	//
+	SubscriberIterator i;
+	i = subscriptions.matchingSubscribers(req.event).iterator();
+	while(i.hasNext()) {
+	    Subscriber s = i.next();
+	    if ((sender == null || !SENP.match(sender, s.identity)) &&
+		(req.ttl > 0 || s.isLocal())) {
+		req.to = s.identity;
+		if (!s.notify(req))
+		    removeUnreachableSubscriber((Subscriber)s);
+	    }
+	}
+    }
+
+    private void subscribe(SENPPacket req)
+	throws InvalidSenderException, SienaException {
+	if (req.filter == null && req.pattern == null) {
+	    //
+	    // null filters/patterns are not allowed in subscriptions
+	    // this is a design choice, we could accept null filters
+	    // with the semantics of the universal filter: one that
+	    // matches every notification
+	    //
+	    Logging.prlnerr("subscribe: null filter/pattern in subscription");
+	    return;
+	}
+
+	Monitor.subscribe(req.id, my_identity);
+	Subscriber s = map_subscriber(req);
+	if (s == null) {
+	    Logging.prlnerr("subscribe: unknown subscriber: "
+			    + req.toString());
+	    return;
+	}
+	if (req.filter != null) {
+	    subscribe(req.filter, s, req);
+	} else {
+	    //
+	    // must be req.pattern != null
+	    //
+	    subscribe(req.pattern, s);
+	}
+    }
+
+    synchronized private void unsubscribe(Filter f, Subscriber s,
+					  SENPPacket req) {
+	//
+	// for global unsubscriptions (f == null)
+	// removes all the patterns as well
+	//
+	if (f == null) unsubscribe((Pattern)null, s);
+	Set to_remove = subscriptions.to_remove(f, s);
+	if (to_remove.isEmpty()) return;
+
+	if (req == null) req = spkt;
+
+	req.id = my_identity;
+	req.handler = listener.uri();
+	req.to = master_id;
+
+	Set new_roots = null;
+	for(Iterator i = to_remove.iterator(); i.hasNext();) {
+	    Subscription sub = (Subscription)i.next();
+	    if (new_roots == null) new_roots = new HashSet();
+
+	    new_roots.addAll(subscriptions.remove(sub));
+	    if (master != null && master_id != null
+		&& !SENP.match(s.identity, master_id) // this should never fail
+		&& req.ttl > 0) {
+		try {
+		    req.method = SENP.UNS;
+		    req.filter = sub.filter;
+		    master.send(req.buf, req.encode());
+		} catch (Exception ex) {
+		    Logging.prlnerr("unsubscribe: error sending packet to master " + master.toString() + ": " + ex.toString());
+		}
+	    }
+	}
+
+	if (new_roots != null) {
+	    if (master != null && master_id != null
+		&& !SENP.match(s.identity, master_id) // this should never fail
+		&& req.ttl > 0) {
+		for(Iterator ri = new_roots.iterator(); ri.hasNext();) {
+		    try {
+			req.method = SENP.SUB;
+			req.filter = ((Subscription)ri.next()).filter;
+			master.send(req.buf, req.encode());
+		    } catch (Exception ex) {
+			Logging.prlnerr("unsubscribe: error sending packet to master " + master.toString() + ": " + ex.toString());
+		    }
+		}
+	    }
+	}
+
+	if (s.hasNoRefs()) {
+	    Logging.prlnlog("removing " + s.toString() + " from contacts list");
+	    contacts.remove(s.getKey());
+	}
+    }
+
+    synchronized private void unsubscribe(SENPPacket req)
+	throws InvalidSenderException, SienaException {
+
+	Subscriber s = find_subscriber(req);
+	if (s == null) return;
+	if (req.pattern != null) {
+	    Monitor.disconnect(req.id, my_identity);
+	    unsubscribe(req.pattern, s);
+	} else {
+	    Monitor.unsubscribe(req.id, my_identity);
+	    unsubscribe(req.filter, s, req);
+	}
+    }
+
+    synchronized private Subscriber map_subscriber(SENPPacket req)
+	throws InvalidSenderException {
+	if (req.id == null)
+	    return null;
+	String id = new String(req.id);
+	Subscriber s = (Subscriber)contacts.get(id);
+	if (s == null) {
+	    if (req.handler != null) {
+		s = new Subscriber(id, sender_factory.createPacketSender(new String(req.handler)));
+		contacts.put(s.getKey(), s);
+	    }
+	} else if (req.handler != null) {
+	    s.mapHandler(sender_factory.createPacketSender(new String(req.handler)));
+	}
+	return s;
+    }
+
+    synchronized private Subscriber map_subscriber(Notifiable notifiable) {
+	Subscriber s = (Subscriber)contacts.get(notifiable);
+	if (s == null) {
+	    s = new Subscriber(notifiable);
+	    contacts.put(s.getKey(), s);
+	}
+	return s;
+    }
+
+    synchronized private Subscriber find_subscriber(SENPPacket req)
+	throws InvalidSenderException {
+	if (req.id == null)
+	    return null;
+	return (Subscriber)contacts.get(new String(req.id));
+    }
+
+    synchronized private Subscriber find_subscriber(Notifiable n) {
+	if (n == null)
+	    return null;
+	return (Subscriber)contacts.get(n);
+    }
+
+    /** closes this dispatcher.
+     *
+     *	If this dispatcher has an active listener then closes the
+     *	active listener.  If this dispatcher has a master server, then
+     *	cancels every subscription with the master server.
+     *	<p>
+     *
+     *	Some implementations of the Java VM do not respond correctly
+     *	to shutdown().  In particular, Sun's jvm1.3rc1-linux is known
+     *	not to work correctly.  Sun's jvm1.3-solaris, jvm1.3-win32,
+     *	and jvm1.2.2-linux work correctly.
+     *
+     *	@see #setReceiver(PacketReceiver)
+     *	@see #setMaster(String)
+     **/
+    synchronized public void shutdown() {
+	disconnectMaster();
+	if (listener != null)
+	    try {
+		listener.shutdown();
+	    }
+	    catch (PacketReceiverException ex) {
+		Logging.prlnerr("error shutting down packet receiver: "
+				+ ex.toString());
+	    }
+	Monitor.remove_node(my_identity);
+    }
+
+    /** removes all subscriptions from any notifiable.
+     *
+     *  clears all the subscriptions from local or remote clients.  In
+     *  case this server has a master server, it instructs the master
+     *  server to clear all the subscriptions associated with this
+     *  dispatcher.  This method is also useful in case this
+     *  dispatcher re-uses both a listener and an identity from a
+     *  previous dispatcher that crashed or did not otherwise shutdown
+     *  properly.
+     *
+     *	@see #shutdown()
+     **/
+    synchronized public void clearSubscriptions() throws SienaException {
+	subscriptions.clear();
+	contacts.clear();
+	if (master != null) {
+	    SENPPacket spkt = SENPPacket.allocate();
+	    spkt.method = SENP.BYE;
+	    spkt.id = my_identity;
+	    spkt.to = master_handler;
+	    try {
+		master.send(spkt.buf, spkt.encode());
+	    } finally {
+		SENPPacket.recycle(spkt);
+	    }
+	}
+    }
+
+    public void publish(Notification e) throws SienaException {
+	SENPPacket spkt = SENPPacket.allocate();
+	//	req.event = new Notification(e);
+	spkt.event = e;
+	spkt.method = SENP.PUB;
+	spkt.id = my_identity;
+	try {
+	    publish(spkt);
+	} finally {
+	    SENPPacket.recycle(spkt);
+	}
+    }
+
+    synchronized public void suspend(Notifiable n) throws SienaException {
+	if (n == null) return;
+	Subscriber s;
+	s = (Subscriber)contacts.get(n);
+	if (s != null) s.suspend();
+    }
+
+    synchronized public void resume(Notifiable n) throws SienaException {
+	if (n == null) return;
+	Subscriber s;
+	s = (Subscriber)contacts.get(n);
+	if (s != null) s.resume();
+    }
+
+    synchronized public void subscribe(Filter f, Notifiable n)
+	throws SienaException {
+	if (f == null) {
+	    //
+	    // null filters are not allowed in subscriptions this is a
+	    // design choice, we could accept null filters with the
+	    // semantics of the universal filter: one that matches
+	    // every notification
+	    //
+	    Logging.prlnerr("subscribe: null filter in subscription");
+	    return;
+	}
+
+	Subscriber s = map_subscriber(n);
+	if (s == null) {
+	    Logging.prlnerr("subscribe: unknown local subscriber");
+	    return;
+	}
+	subscribe(f, s, null);
+    }
+
+    synchronized private void subscribe(Filter f, Subscriber s,
+					SENPPacket req) throws SienaException {
+	Subscription sub = subscriptions.insert_subscription(f, s);
+	if (sub == null) return;
+
+	if (subscriptions.is_root(sub) && master != null
+	    && (req == null || req.ttl > 0)) {
+	    try {
+		if (req == null) { req = spkt; req.init(); }
+		req.method = SENP.SUB;
+		req.id = my_identity;
+		req.handler = listener.uri();
+		req.to = (master_id != null) ? master_id : master_handler;
+		req.filter = f;
+		master.send(req.buf, req.encode());
+	    } catch (Exception ex) {
+		Logging.prlnerr("error sending packet to " + master.toString());
+		Logging.exerr(ex);
+		//
+		// log something here ...work in progress...
+		//
+	    }
+	}
+    }
+
+    synchronized public void subscribe(Pattern p, Notifiable n)
+	throws SienaException {
+	Subscriber s = map_subscriber(n);
+	if (s == null) return;
+	subscribe(p,s);
+    }
+
+    synchronized private void subscribe(Pattern p, Subscriber s)
+	throws SienaException {
+	//
+	// warning: this is a naive implementation.
+	// ...work in progress...
+	//
+	PatternMatcher m;
+
+	ListIterator li = matchers.listIterator();
+	while(li.hasNext()) {
+	    m = (PatternMatcher)li.next();
+	    if (Covering.covers(m.pattern, p)) {
+		if (m.subscribers.contains(s))
+		    return;
+		if (Covering.covers(p, m.pattern)) {
+		    m.subscribers.add(s);
+		    return;
+		}
+	    } else if (Covering.covers(p, m.pattern)) {
+		if (m.subscribers.remove(s)) {
+		    if (m.subscribers.isEmpty()) {
+			for(int j = 0; j < m.pattern.filters.length; ++j)
+			    unsubscribe(m.pattern.filters[j], m);
+			//
+			// FIXME: this is a bug!  in fact this
+			// li.remove() is executed right before a
+			// li.hasNext()---the problem is the screwed
+			// up semantics of iterators in Java... which
+			// may make sense for a generic iterator, but
+			// is incredibly stupid for a list iterator:
+			// it should be possible to call li.remove()
+			// and assume that li shifted to the next
+			// element (so that li.hasNext() would work as
+			// expected).  This demonstrates once again
+			// that defining collections and iterators
+			// with generic classes is not always a good
+			// idea ... well, enough flames for now.
+			// Anyway, this needs to be fixed!
+			//
+			li.remove();
+		    }
+		}
+	    }
+	}
+
+	m = new PatternMatcher(p, my_identity);
+	m.subscribers.add(s);
+	for(int i = 0; i < p.filters.length; ++i)
+	    subscribe(p.filters[i], m);
+	matchers.add(m);
+    }
+
+    synchronized public void unsubscribe(Filter f, Notifiable n) {
+	Subscriber s = find_subscriber(n);
+	if (s == null) return;
+	unsubscribe(f, s, null);
+    }
+
+    synchronized public void unsubscribe(Pattern p, Notifiable n) {
+	Subscriber s = map_subscriber(n);
+	if (s == null) return;
+	unsubscribe(p,s);
+    }
+
+    synchronized private void unsubscribe(Pattern p, Subscriber s) {
+	//
+	// warning: this is a naive implementation.
+	// ...work in progress...
+	//
+	PatternMatcher m;
+
+	ListIterator li = matchers.listIterator();
+	while(li.hasNext()) {
+	    m = (PatternMatcher)li.next();
+	    if (p == null || Covering.covers(p, m.pattern)) {
+		if (m.subscribers.remove(s)) {
+		    if (m.subscribers.isEmpty()) {
+			for(int j = 0; j < m.pattern.filters.length; ++j)
+			    unsubscribe(m.pattern.filters[j], m);
+			//
+			// FIXME: this is a bug!  see same comment above
+			//
+			li.remove();
+		    }
+		}
+	    }
+	}
+	if (s.hasNoRefs()) {
+	    Logging.prlnlog("removing " + s.toString() + " from contacts list");
+	    contacts.remove(s.getKey());
+	}
+    }
+
+    /** this method has no effect.
+     *
+     *	Method not implemented in this Siena server.
+     **/
+    public void advertise(Filter f, String id) throws SienaException {};
+
+    /** this method has no effect.
+     *
+     *  Method not implemented in this Siena server.
+     **/
+    public void unadvertise(Filter f, String id) throws SienaException {};
+
+    /** this method has no effect.
+     *
+     *	Method not implemented in this Siena server.
+     **/
+    public void unadvertise(String id) throws SienaException {};
+
+    synchronized public void unsubscribe(Notifiable n) throws SienaException {
+	unsubscribe((Filter)null, n);
+    }
+}
diff --git a/siena/ipaq/InvalidSenderException.java b/siena/ipaq/InvalidSenderException.java
new file mode 100644
index 0000000..9448d1f
--- /dev/null
+++ b/siena/ipaq/InvalidSenderException.java
@@ -0,0 +1,43 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This program is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU General Public License
+//  as published by the Free Software Foundation; either version 2
+//  of the License, or (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.lang.String;
+
+/** invalid Siena handler.
+ *
+ *    this exception indicates a malformed or unknown handler.  The
+ *    current implementation provides only the "senp" (Siena Event
+ *    Notification Protocol) handler.
+ *    <p>
+ **/
+public class InvalidSenderException extends SienaException {
+    public InvalidSenderException(String s) {
+	super(s);
+    }
+}
diff --git a/siena/ipaq/KAPacketReceiver.java b/siena/ipaq/KAPacketReceiver.java
new file mode 100644
index 0000000..3bdac4f
--- /dev/null
+++ b/siena/ipaq/KAPacketReceiver.java
@@ -0,0 +1,412 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2000 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+// $Id$
+//
+
+package siena;
+
+import com.sun.java.util.collections.LinkedList;
+import java.io.InterruptedIOException;
+import java.io.IOException;
+import java.io.EOFException;
+import java.io.InputStream;
+import java.io.BufferedInputStream;
+import java.net.UnknownHostException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+
+class KADescrQueue {
+    private int		size = 0;
+    private KAConnDescr first = null;
+    private KAConnDescr last = null;
+    private KAConnDescr available = null;
+    private boolean	active = true;
+
+    synchronized int size() { return size; }
+
+    synchronized void put(Socket s) {
+	KAConnDescr kd;
+
+	if (available == null) {
+	    kd = new KAConnDescr(s);
+	} else {
+	    kd = available;
+	    available = available.next;
+	    kd.init(s);
+	}
+	if (last != null) last.next = kd;
+	last = kd;
+	kd.next = null;
+	if (first == null) first = kd;
+	++size;
+	notify();
+    }
+
+    synchronized void put(KAConnDescr kd) {
+	if (last != null) last.next = kd;
+	last = kd;
+	kd.next = null;
+	if (first == null) first = kd;
+	++size;
+	notify();
+    }
+
+    synchronized KAConnDescr get() throws InterruptedException {
+	KAConnDescr kd;
+	while(first == null) {
+	    if (!active) return null;
+	    wait();
+	}
+	kd = first;
+	first = kd.next;
+	kd.next = null;
+	if (last == kd) last = null;
+	--size;
+	return kd;
+    }
+
+    synchronized void recycle(KAConnDescr kd) {
+	if (kd.sock != null)
+	    try { kd.sock.close(); } catch (IOException ex) { }
+	kd.sock = null;
+	kd.stream = null;
+	kd.next = available;
+	available = kd;
+    }
+
+    synchronized void shutdown() {
+	KAConnDescr kd = first;
+	while(kd != null) {
+	    kd.close();
+	    kd = kd.next;
+	}
+	available = null;
+	last = null;
+	size = 0;
+	active = false;
+	notifyAll();
+    }
+}
+
+class KAConnDescr {
+    Socket	sock;
+    InputStream stream;
+    int		count;
+    long	last_good;
+    KAConnDescr next;
+
+    public KAConnDescr(Socket s) {
+	init(s);
+    }
+
+    public void init(Socket s)  {
+	sock = s;
+	stream = null;
+	count = 0;
+    	last_good = System.currentTimeMillis();
+    }
+
+    synchronized public void close() {
+	stream = null;
+	if (sock != null)
+	    try {
+		sock.close();
+	    } catch (IOException ex) {
+		//
+		// what else should I do here?
+		//
+	    }
+	sock = null;
+    }
+
+    synchronized public int receive(byte [] buf)
+	throws InterruptedException, IOException {
+	if (sock == null) return -1;
+	if (stream == null)
+	    stream = new BufferedInputStream(sock.getInputStream());
+	int res = 0;
+	int len = 0;
+	int len1 = 0;
+	int pos = 0;
+	//
+	// I need to handle timout on read a bit better...
+	// ...work in progress...
+	//
+	sock.setSoTimeout(5000);
+
+	//
+	// we read the msb and lsb of the length.  We break if
+	// we can't read them or if the resulting length is 0
+	//
+	len = stream.read();
+	if (len < 0) return -1;
+	len1 = stream.read();
+	if (len1 < 0) return -1;
+	len = (len << 8) | (len1 & 0xff);
+	if (len < 0) return -1;
+	pos = 0;
+	while(pos < len) {
+	    if ((res = stream.read(buf,pos, len - pos)) < 0)
+		return -1;
+	    pos += res;
+	}
+	//	    last_good = System.currentTimeMillis();
+	++count;
+	return len;
+    }
+}
+
+/** receives packets through a TCP port.
+ *
+ *  Uses persistent TCP connections to receive one or more packets.
+ **/
+public class KAPacketReceiver implements PacketReceiver, Runnable {
+    public static final byte	REPLY_REJECT	= 0;
+    public static final byte	REPLY_OK	= 1;
+
+    static final String		protocol_name = "ka+senp";
+    private ServerSocket	port;
+    private Socket[]		connection;
+    private byte[]		my_uri;
+    private Thread		acceptor;
+
+    static public int		DefaultAcceptTimeout = 5000;
+    public int			accept_timeout = DefaultAcceptTimeout;
+
+    static public int		DefaultReceiveTimeout = 3000;
+    public int			receive_timeout = DefaultReceiveTimeout;
+
+    static public int		DefaultMaxReceiveCount = -1;
+    public int			receive_max_count = DefaultMaxReceiveCount;
+
+    static public int		DefaultMaxActiveConnections = 20;
+    public int			max_active_connections = DefaultMaxActiveConnections;
+
+    private KADescrQueue	active_connections = new KADescrQueue();
+
+    /** create a receiver listening to the a random port.
+     *
+     *  @exception IOException if an I/O error occurs when opening the
+     *		socket port.
+     **/
+    public KAPacketReceiver() throws IOException {
+	port = new ServerSocket(0);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+
+	acceptor = new Thread(this);
+	acceptor.start();
+    }
+
+    /** create a receiver listening to the given port.
+     *
+     *  @param port_number must be a valid TCP port number, or it can
+     *         be 0 in which case a random port is used
+     *
+     *  @exception IOException if an I/O error occurs when opening the
+     *		socket.  typically, when the given port is already in use.
+     **/
+    public KAPacketReceiver(int port_number) throws IOException {
+	port = new ServerSocket(port_number);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+
+	acceptor = new Thread(this);
+	acceptor.start();
+    }
+
+    /** create a receiver listening to the given port with a given
+     *  maximum queue for TCP connections.
+     *
+     *  @param port_number must be a valid TCP port number, or it can
+     *		be 0 in which case a random port is used
+     *
+     *  @exception IOException if an I/O error occurs when opening the
+     *		socket.  typically, when the given port is already in use.
+     **/
+    public KAPacketReceiver(int port_number, int qsize)
+	throws IOException {
+	port = new ServerSocket(port_number, qsize);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** create a receiver listening to the given port.
+     *
+     *  @param s server socket used to accept connections.
+     *
+     *  @exception UnknownHostException if an error occurrs while
+     *		resolving the hostname for this host.
+     **/
+    public KAPacketReceiver(ServerSocket s) throws UnknownHostException {
+	port = s;
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** explicitly set the address of this packet receiver.
+     *
+     *  This method allows to set the host name or IP address
+     *  explicitly.  This might be necessary in the cases in which the
+     *  Java VM can not reliably figure that out by itself.
+     **/
+    synchronized public void setHostName(String hostname) {
+	my_uri = (protocol_name + "://" + hostname + ":"
+		  + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** uri of this packet receiver.
+     *
+     *  uses the following schema syntax:<br>
+     *
+     *  <code>senp://</code><em>host</em><em>[<code>:</code>port]</em>
+     **/
+    public byte[] uri() {
+	return my_uri;
+    }
+
+    synchronized public void shutdown() {
+	if (port == null) return;
+	try {
+	    port.close();
+	    //
+	    // this should terminate all the connection handlers
+	    // attached to that port.  They should receive an
+	    // IOException on accept() ...they should, but on some
+	    // implementations of the JVM they don't.  In
+	    // particular, jvm-1.3rc1-linux-i386 is buggy.
+	    //
+	} catch (IOException ex) {
+	    Logging.exerr(ex);
+	    //
+	    // what can I do here? ...work in progress...
+	    //
+	}
+	port = null;
+	active_connections.shutdown();
+    }
+
+    //
+    // acceptor thread routine
+    //
+    public void run() {
+	Socket s;
+ 	try {
+ 	    port.setSoTimeout(accept_timeout);
+ 	} catch (SocketException ex) {
+ 	    Logging.prlnerr("error setting SO_TIMOUT for server socket "
+ 			    + port.toString());
+ 	    Logging.prlnerr(ex.toString());
+ 	    //
+ 	    // what can I do here? ...work in progress...
+ 	    //
+ 	}
+
+	while(port != null) {
+	    try {
+		s = port.accept();
+		if (max_active_connections < 0 ||
+		    active_connections.size() < max_active_connections) {
+		    //		    s.setReceiveBufferSize(65535); // I made this up...
+		    s.getOutputStream().write(REPLY_OK);
+		    //s.shutdownOutput();
+		    active_connections.put(s);
+		} else {
+		    //
+		    // reject this one
+		    //
+		    Logging.prlnlog("rejecting KA connection");
+		    s.getOutputStream().write(REPLY_REJECT);
+		    s.close();
+		}
+	    } catch (InterruptedIOException ex) {
+		// do nothing here.  we will simply get out of the
+		// while loop if port == null...
+	    } catch (IOException ex) {
+		//
+		// I interpret this as a shutdown()
+		//
+		shutdown();
+		return;
+	    }
+	    s = null;
+	}
+    }
+
+    public int receive(byte [] buf) throws PacketReceiverException {
+	KAConnDescr kd;
+	int res;
+	while (port != null) {
+	    try {
+		kd = active_connections.get();
+	    } catch (InterruptedException ex) {
+		throw new PacketReceiverException(ex.toString());
+	    }
+	    if (kd == null) throw new PacketReceiverClosed();
+	    res = -1;
+	    try {
+		res = kd.receive(buf);
+	    } catch (EOFException ex) {
+		//
+		// we simply ignore this exception, and continue the loop
+		// as long as port != null
+		//
+	    } catch (InterruptedIOException ex) {
+		//
+		// we simply ignore this exception, recycle the
+		// socket, and continue the loop as long as port !=
+		// null
+		//
+	    } catch (Exception ex) {
+		ex.printStackTrace();
+		throw new PacketReceiverException(ex.toString());
+	    } finally {
+		if (res < 0) {
+		    if (port != null)
+			active_connections.recycle(kd);
+		} else {
+		    active_connections.put(kd);
+		    return res;
+		}
+	    }
+	}
+	throw new PacketReceiverClosed();
+    }
+
+    /** <em>not yet implemented</em>.
+     **/
+    public int receive(byte[] buf, long timeout) {
+	//
+	// not yet implemented
+	//
+	return -1;
+    }
+}
diff --git a/siena/ipaq/KAPacketSender.java b/siena/ipaq/KAPacketSender.java
new file mode 100644
index 0000000..e148cf6
--- /dev/null
+++ b/siena/ipaq/KAPacketSender.java
@@ -0,0 +1,130 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.IOException;
+
+class KAPacketSender implements PacketSender {
+    private InetAddress		ip_address;
+    private int			port;
+    private Socket		socket;
+    private OutputStream	os;
+    private long		last_conn = -1;
+
+    public int			DefaultSoTimeout = 5000;
+    private int			so_timeout = DefaultSoTimeout;
+
+    public int			DefaultDisconnectTimeout = 5000;
+    private int			disconnect_timeout = DefaultSoTimeout;
+
+
+    public KAPacketSender(String h)
+	throws InvalidSenderException, java.net.UnknownHostException {
+
+	if (h.indexOf("//", 0) != 0)
+	    throw (new InvalidSenderException("expecting `//'"));
+
+	int port_end_pos = -1;
+	int host_end_pos = h.indexOf(":", 2);
+
+	if (host_end_pos < 0) {
+	    port = -1;
+	    host_end_pos = h.indexOf("/", 2);
+	    if (host_end_pos < 0) host_end_pos = h.length();
+	} else {
+	    port_end_pos = h.indexOf("/", host_end_pos);
+	    if (port_end_pos < 0) port_end_pos = h.length();
+
+	    if (host_end_pos+1 < port_end_pos) {
+		port = Integer.decode(h.substring(host_end_pos+1,
+						  port_end_pos)).intValue();
+	    } else {
+		port = -1;
+	    }
+	}
+	String hostname = h.substring(2, host_end_pos);
+	if (port == -1) {
+	    port = SENP.SERVER_PORT;
+	}
+	ip_address = InetAddress.getByName(hostname);
+    }
+
+    public void send(byte[] packet) throws PacketSenderException {
+	send(packet, 0, packet.length);
+    }
+
+    public void send(byte[] packet, int offset, int len)
+	throws PacketSenderException {
+	int retries;
+	retries = 0;
+	while (true) {
+	    try {
+		if (socket == null) {
+		    try {
+			socket = new Socket(ip_address, port);
+		    } catch (IOException ex) {
+			throw new PacketSenderException(ex.getMessage());
+		    }
+		    if (socket.getInputStream().read()!=KAPacketReceiver.REPLY_OK)
+			throw new PacketSenderException("connection rejected");
+
+		    //		    socket.shutdownInput();
+		    //		    socket.setSoTimeout(so_timeout);
+		    //		    socket.setSendBufferSize(65535);
+		    os = socket.getOutputStream();
+		}
+		synchronized (os) {
+		    os.write((len & 0xff00) >>> 8);
+		    os.write(len & 0xff);
+		    os.write(packet, offset, len);
+		    os.flush();
+		}
+		return;
+	    } catch (IOException ex) {
+		socket = null;
+		os = null;
+		if (++retries > 1)
+		    throw new PacketSenderException(ex.toString());
+	    }
+	}
+    }
+
+    public void send(byte[] packet, int len)
+	throws PacketSenderException {
+	send(packet, 0, len);
+    }
+
+    public String toString() {
+	return KAPacketReceiver.protocol_name + "://" + ip_address + ":" + port;
+    }
+}
+
diff --git a/siena/ipaq/Logging.java b/siena/ipaq/Logging.java
new file mode 100644
index 0000000..5106177
--- /dev/null
+++ b/siena/ipaq/Logging.java
@@ -0,0 +1,114 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.io.PrintStream;
+import java.util.Date;
+
+/**
+ *   logging and error reporting facility for Siena.
+ *
+ *   <code>Logging</code> allows you to redirect error and log messages
+ *   to specific streams.
+ **/
+public class Logging {
+    static PrintStream	log = null;
+    static PrintStream	err = System.err;
+
+    static private String time() {
+	return (new Date()).toString() + ": ";
+    }
+
+    synchronized static void exerr(Exception ex) {
+	if (err != null) {
+	    err.print(time());
+	    ex.printStackTrace(err);
+	}
+    }
+
+    synchronized static void exlog(Exception ex) {
+	if (log != null) {
+	    log.print(time());
+	    ex.printStackTrace(log);
+	}
+    }
+
+    synchronized static void prerr(String s) {
+	if (err != null) err.print(time() + s);
+    }
+
+    synchronized static void prlog(String s) {
+	if (log != null) log.print(time() + s);
+    }
+
+    synchronized static void prlnerr(String s) {
+	if (err != null) err.println(time() + s);
+    }
+
+    synchronized static void prlnlog(String s) {
+	if (log != null) log.println(time() + s);
+    }
+
+    /** sets a log and debug stream.  <code>null</code> means no log
+        and debug output.
+
+        @param d the new debug output stream
+        @see #getLogStream() */
+    synchronized static public void setLogStream(PrintStream s) {
+	log = s;
+    }
+
+    /** the current debug output stream. <code>null</code> means no
+        debug output.
+
+        @return the current debug output stream.
+        @see #setLogStream(PrintStream)
+    */
+    synchronized static public PrintStream getLogStream() {
+	return log;
+    }
+
+    /** sets an error output stream.  <code>null</code> means no error
+        output.
+
+        @param d the new error output stream
+        @see #getErrorStream() */
+    synchronized static public void setErrorStream(PrintStream s) {
+	err = s;
+    }
+
+    /** the current error output stream. <code>null</code> means no
+        error output.
+
+        @return the current error output stream.
+        @see #setErrorStream(PrintStream)
+    */
+    synchronized static public PrintStream getErrorStream() {
+	return err;
+    }
+}
diff --git a/siena/ipaq/Monitor.java b/siena/ipaq/Monitor.java
new file mode 100644
index 0000000..a6c3031
--- /dev/null
+++ b/siena/ipaq/Monitor.java
@@ -0,0 +1,266 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2001 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.io.IOException;
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+import java.net.InetAddress;
+
+/** Monitor is a logging facility that can be used in conjunction with
+ *  the <a href="http://www.cs.colorado.edu/serl/siena/smon/">Siena
+ *  Monitor</a>.
+ **/
+class Monitor {
+    private static final int		CmdLineMaxLen	= 1024;
+    private static final int		DefaultPort	= 1996;
+
+    private static DatagramSocket	socket		= null;
+    private static DatagramPacket	packet		= null;
+    private static byte[]		data		= null;
+    private static int			len;
+
+    private static boolean initialize() {
+	String hostname = System.getProperty("SienaMonitor");
+	if (hostname == null)
+	    return false;
+	InetAddress address;
+	int port;
+	String portnumber = System.getProperty("SienaMonitorPort");
+
+	try {
+	    address = InetAddress.getByName(hostname);
+	    if (portnumber != null) {
+		port = Integer.parseInt(portnumber);
+	    } else {
+		port = DefaultPort;
+	    }
+	    data = new byte[CmdLineMaxLen];
+	    len = 0;
+	    socket = new DatagramSocket();
+	    packet = new DatagramPacket(data, len, address, port);
+
+	    return true;
+	} catch (IOException ex) {
+	    Logging.prlnerr("error initializing monitor for hostname "
+			    + hostname);
+	    Logging.prlnerr(ex.toString());
+	    return false;
+	}
+    }
+    /** sets the address of the Siena  Monitor.
+     *
+     *  uses the default port.
+     **/
+    public static void setAddress(InetAddress address) {
+	setAddress(address, DefaultPort);
+    }
+
+    /** sets address and port for the Siena Monitor
+     **/
+    public static void setAddress(InetAddress address, int port) {
+	try {
+	    data = new byte[CmdLineMaxLen];
+	    len = 0;
+	    socket = new DatagramSocket();
+	    packet = new DatagramPacket(data, len, address, port);
+	    active = true;
+	} catch (IOException ex) {
+	    Logging.prlnerr("error initializing monitor for address "
+			    + address);
+	    Logging.prlnerr(ex.toString());
+	    active = false;
+	}
+    }
+
+    private static boolean active = initialize();
+
+    private static final byte	SEP			= 0x20;
+
+    private static final byte[]	Add
+    = { 0x61, 0x64, 0x64, 0x5f, 0x6e, 0x6f, 0x64, 0x65 };
+    private static final byte[]	Remove
+    = { 0x72, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x5f, 0x6e, 0x6f, 0x64, 0x65 };
+    private static final byte[]	Connect
+    = { 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74 };
+    private static final byte[]  Disconnect
+    = { 0x64, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74 };
+    private static final byte[]  Notify
+    = { 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x79 };
+    private static final byte[]  Subscribe
+    = { 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65 };
+    private static final byte[]  Unsubscribe
+    = { 0x75, 0x6e, 0x73, 0x75, 0x62, 0x73, 0x63, 0x72, 0x69, 0x62, 0x65 };
+
+    /** a node representing a Siena server **/
+    public static final byte[] SienaNode
+    = { 0x73, 0x69, 0x65, 0x6e, 0x61 };
+    /** a node representing a thin client **/
+    public static final byte[] ThinClientNode
+    = { 0x74, 0x68, 0x69, 0x6e, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74 };
+    /** a node representing a generic object **/
+    public static final byte[] ObjectNode
+    = { 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74 };
+    /** a smiley face **/
+    public static final byte[] AntoNode
+    = { 0x61, 0x6e, 0x74, 0x6f };
+    //
+    // to be continued with more icons ...work in progress...
+    //
+
+    private static void shipout() {
+	try {
+	    packet.setLength(len);
+	    socket.send(packet);
+	    len = 0;
+	} catch (IOException ex) {
+	    //Logging.prlnerr("error sending log message to monitor: "
+	    //		    + socket.getInetAddress()
+	    //		    + ":" + socket.getPort());
+	    Logging.prlnerr(ex.toString());
+	}
+    }
+
+    private static boolean append(byte[] x) {
+	int i = 0;
+	while(i < x.length) {
+	    if (len == CmdLineMaxLen) return false;
+	    data[len++] = x[i++];
+	}
+	if (len == CmdLineMaxLen) return false;
+	data[len++] = SEP;
+	return true;
+    }
+
+    /** Signal a notification to the monitor.
+     *
+     * @param sender id of the sender
+     * @param receiver id of the receiver
+     **/
+    synchronized public static void notify(byte[] sender, byte[] receiver) {
+	if (!active) return;
+	if (!append(Notify)) return;
+	if (!append(sender)) return;
+	if (!append(receiver)) return;
+	shipout();
+    }
+
+    /** Signal a subscription to the monitor.
+     *
+     * @param sender id of the subscriber
+     * @param receiver id of the receiver node
+     **/
+    synchronized public static void subscribe(byte[] sender, byte[] receiver) {
+	if (!active) return;
+	if (!append(Subscribe)) return;
+	if (!append(sender)) return;
+	if (!append(receiver)) return;
+	shipout();
+    }
+
+    /** Signal an unsubscription to the monitor.
+     *
+     * @param sender id of the subscriber
+     * @param receiver id of the receiver node
+     **/
+    synchronized public static void unsubscribe(byte[] sender,
+						byte[] receiver) {
+	if (!active) return;
+	if (!append(Unsubscribe)) return;
+	if (!append(sender)) return;
+	if (!append(receiver)) return;
+	shipout();
+    }
+
+    /** Signal a connection to the monitor.
+     *
+     * @param n1 id of the first node
+     * @param n2 id of the second node
+     **/
+    synchronized public static void connect(byte[] n1, byte[] n2) {
+	if (!active) return;
+	if (!append(Connect)) return;
+	if (!append(n1)) return;
+	if (!append(n2)) return;
+	shipout();
+    }
+
+    /** Signal a disconnection to the monitor.
+     *
+     * @param n1 id of the first node
+     * @param n2 id of the second node
+     **/
+    synchronized public static void disconnect(byte[] n1, byte[] n2) {
+	if (!active) return;
+	if (!append(Disconnect)) return;
+	if (!append(n1)) return;
+	if (!append(n2)) return;
+	shipout();
+    }
+
+    /** Signal a new "node" to the monitor.
+     *
+     * @param node id of the new node
+     * @param ntype type of the new node
+     **/
+    synchronized public static void add_node(byte[] node, byte[] ntype) {
+	if (!active) return;
+	if (!append(Add)) return;
+	if (!append(node)) return;
+	if (!append(ntype)) return;
+	shipout();
+    }
+
+    /** Signal the creation of a a new node to the monitor
+     *
+     *  uses the Siena server icond, by default
+     *
+     * @param node id of the new node
+     **/
+    public static void add_node(byte[] node) {
+	if (!active) return;
+	if (!append(Add)) return;
+	if (!append(node)) return;
+	shipout();
+    }
+
+    /** Signal the removal of a a new node
+     *
+     * @param node id of the removed node
+     **/
+    public static void remove_node(byte[] node) {
+	if (!active) return;
+	if (!append(Remove)) return;
+	if (!append(node)) return;
+	shipout();
+    }
+}
+
+
+
+
diff --git a/siena/ipaq/Notifiable.java b/siena/ipaq/Notifiable.java
new file mode 100644
index 0000000..c1a6cd1
--- /dev/null
+++ b/siena/ipaq/Notifiable.java
@@ -0,0 +1,94 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** interface implemented by event consumers.
+ *
+ *    Every object that wants to receive event notifications from
+ *    Siena must implement this interface.  Siena calls
+ *    <code>notify(Notification)</code> on a subscriber to notify a
+ *    single event to it.  Siena calls <code>notify(Notification
+ *    [])</code> to notify a sequence of events.<p>
+ *
+ *    Example:
+ *    <pre><code>
+ *    class SimpleSubscriber implements Notifiable {
+ *
+ *        public void notify(Notification e) {
+ *            System.out.println("I got this notification: " + e.toString());
+ *        }
+ *
+ *        public void notify(Notification s[]) {
+ *            // I never subscribe for patterns anyway.
+ *        }
+ *    }
+ *    </pre></code>
+ *
+ *   @see Notification
+ *   @see Siena
+ **/
+public interface Notifiable {
+
+    /** sends a <code>Notification</code> to this <code>Notifable</code>
+     *
+     *  Since version 1.0.1 of the Siena API it is safe to modify the
+     *  Notification object received through this method.  Note that:
+     *  <ol>
+     *
+     *  <li><em>any</em> previous version of the Siena API assumes that
+     *      clients <em>do not modify</em> these notifications;
+     *
+     *  <li>the current solution incurrs in an unnecessary cost by
+     *      having to duplicate every notification.  Therefore, it
+     *      is a <em>temporary solution</em>.  The plan is to
+     *      implement <em>immutable</em> notifications and to pass
+     *      those to subscribers.
+     *
+     *  </ol>
+     *  necessary duplication of notifications can be expensive,
+     *  especially if the same notification must be copied to numerous
+     *  subscribers.
+     *
+     *  @param n notification passed to the notifiable
+     *  @see Siena#subscribe(Filter,Notifiable)
+     **/
+    public void notify(Notification n) throws SienaException;
+
+    /** sends a sequence of <code>Notification</code>s to this
+     *  <code>Notifable</code>
+     *
+     *  Since version 1.0.1 of the Siena API it is safe to modify the
+     *  Notification objects received through this method.  Please
+     *  read the notes in the above documentation of {@link
+     *  #notify(Notification)}, which apply to this method as well.
+     *
+     *  @param s sequence of notifications passed to the notifiable
+     *  @see Siena#subscribe(Pattern,Notifiable)
+     **/
+    public void notify(Notification s[]) throws SienaException;
+}
diff --git a/siena/ipaq/Notification.java b/siena/ipaq/Notification.java
new file mode 100644
index 0000000..191f3c0
--- /dev/null
+++ b/siena/ipaq/Notification.java
@@ -0,0 +1,215 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import com.sun.java.util.collections.Map;
+import com.sun.java.util.collections.HashMap;
+import com.sun.java.util.collections.Iterator;
+
+/** an event notification
+ *
+ *  The primary data entity used within Siena.  A notification is
+ *  structured as a set of named and typed attributes.  Attribute
+ *  names are strings. <p>
+ *
+ *  A valid attribute name must begin with a letter
+ *  (<code>'a'</code>-<code>'z'</code>,
+ *  <code>'A'</code>-<code>'Z'</code>) or an underscore character
+ *  (<code>'_'</code>), and may contain only letters, underscores,
+ *  digits (<code>'0'</code>-<code>'9'</code>), the dot character
+ *  (<code>'.'</code>), the forward slash character
+ *  (<code>'/'</code>), and the dollar sign
+ *  (<code>'$'</code>). Attribute names must be unique within a
+ *  <code>Notification</code>.  <p>
+ *
+ *  Example:
+ *  <p>
+ *  <pre><code>
+ *      Notification alert = new Notification();
+ *      alert.putAttribute("threat", "virus");
+ *      alert.putAttribute("name", "melissa");
+ *      alert.putAttribute("total_infected", 25);
+ *      alert.putAttribute("os/name", "win32");
+ *      alert.putAttribute("os/version", "98");
+ *  </pre></code>
+ *
+ *  @see AttributeValue
+ *  @see Filter
+ *  @see Siena#publish(Notification)
+ **/
+public class Notification  implements java.io.Serializable {
+    Map attributes;
+
+    /** constructs an empty notification.
+     **/
+    public Notification() {
+	attributes = new HashMap();
+    }
+
+    /** creates a deep copy of a given notification.
+     **/
+    public Notification(Notification n) {
+	attributes = new HashMap();
+	for(Iterator i = n.attributes.entrySet().iterator(); i.hasNext();) {
+	    Map.Entry entry = (Map.Entry)i.next();
+	    attributes.put((String)entry.getKey(), new AttributeValue((AttributeValue)entry.getValue()));
+	}
+    }
+
+    private void writeObject(java.io.ObjectOutputStream out)
+	throws java.io.IOException {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	out.writeInt(b.length());
+	out.write(b.buf);
+    }
+
+    private void readObject(java.io.ObjectInputStream in)
+	throws java.io.IOException, java.lang.ClassNotFoundException {
+	int len = in.readInt();
+	SENPBuffer b = new SENPBuffer();
+	in.readFully(b.buf, 0, len);
+	b.init(len);
+
+	Notification n;
+	try {
+	    n = b.decodeNotification();
+	} catch (SENPInvalidFormat ex) {
+	    throw new java.io.InvalidObjectException(ex.toString());
+	}
+	attributes = new HashMap();
+	for(Iterator i = n.attributes.entrySet().iterator(); i.hasNext();) {
+	    Map.Entry entry = (Map.Entry)i.next();
+	    attributes.put((String)entry.getKey(), new AttributeValue((AttributeValue)entry.getValue()));
+	}
+    }
+
+    /** returns an iterator over the set of attribute names.
+     *
+     *  @deprecated as of Siena 1.1.2
+     **/
+    public Iterator iterator() {
+ 	return attributes.keySet().iterator();
+    }
+
+    /** set the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value String value.
+     **/
+    public void putAttribute(String name, String value) {
+	attributes.put(name, new AttributeValue(value));
+    }
+
+    /** sets the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value byte array value.
+     **/
+    public void putAttribute(String name, byte[] value) {
+	attributes.put(name, new AttributeValue(value));
+    }
+
+    /** set the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value integer value.
+     **/
+    public void putAttribute(String name, long value) {
+	attributes.put(name, new AttributeValue(value));
+    }
+
+    /** set the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value double value.
+     **/
+    public void putAttribute(String name, double value) {
+	attributes.put(name, new AttributeValue(value));
+    }
+
+    /** set the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value boolean value.
+     **/
+    public void putAttribute(String name, boolean value) {
+	attributes.put(name, new AttributeValue(value));
+    }
+
+    /** set the value of an attribute.
+     *
+     *  Add the attribute if that is not present.
+     *  @param name attribute name.
+     *  @param value value.
+     **/
+    public void putAttribute(String name, AttributeValue value) {
+	attributes.put(name, value);
+    }
+
+    /** returns the value of an attribute or <code>null</code> if
+     *  that attribute does not exist in this notification.
+     *
+     *  @param name attribute name.
+     **/
+    public AttributeValue getAttribute(String name) {
+	return (AttributeValue)attributes.get(name);
+    }
+
+    /** returns the number of attributes in this notification.
+     *
+     *  @param name attribute name.
+     **/
+    public int size() {
+	return attributes.size();
+    }
+
+    /** removes every attribute from this notification.
+     **/
+    public void clear() {
+	attributes.clear();
+    }
+
+    /** returns an iterator for the set of attribute names of this
+     *	notification.
+     **/
+    public Iterator attributeNamesIterator() {
+	return attributes.keySet().iterator();
+    }
+
+    public String toString() {
+	SENPBuffer b = new SENPBuffer();
+	b.encode(this);
+	return new String(b.buf, 0, b.length());
+    }
+}
diff --git a/siena/ipaq/NotificationBuffer.java b/siena/ipaq/NotificationBuffer.java
new file mode 100644
index 0000000..2aaec0b
--- /dev/null
+++ b/siena/ipaq/NotificationBuffer.java
@@ -0,0 +1,260 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import com.sun.java.util.collections.LinkedList;
+
+interface ObjectBuffer {
+    public void put(Object o);
+    public boolean isEmpty();
+    public Object get();
+    public int size();
+}
+
+class StaticBuffer implements ObjectBuffer {
+    int count = 0;
+    int first = 0;
+    Object [] objs;
+    public StaticBuffer(int dim) {
+	objs = new Object[dim];
+    }
+
+    synchronized public void put(Object o) {
+	if (count == objs.length) {
+	    objs[first] = o;
+	    first = (first + 1) % objs.length;
+	} else {
+	    objs[(first + count) % objs.length] = o;
+	    ++count;
+	}
+    }
+
+    synchronized public boolean isEmpty() {
+	return count == 0;
+    }
+
+    synchronized public Object get() {
+	if (count == 0) return null;
+	Object res = objs[first];
+	objs[first] = null;
+	first = (first + 1) % objs.length;
+	--count;
+	return res;
+    }
+    synchronized public int size() {
+	return count;
+    }
+}
+
+class DynamicBuffer implements ObjectBuffer {
+    LinkedList objs;
+
+    public DynamicBuffer() {
+	objs = new LinkedList();
+    }
+
+    synchronized public void put(Object o) {
+	objs.addLast(o);
+    }
+
+    synchronized public boolean isEmpty() {
+	return objs.isEmpty();
+    }
+
+    synchronized public Object get() {
+	if (objs.isEmpty()) return null;
+	Object res = objs.getFirst();
+	objs.removeFirst();
+	return res;
+    }
+
+    synchronized public int size() {
+	return objs.size();
+    }
+}
+
+/** a "mailbox" for notifications
+ *
+ *  functions as a proxy notifiable.  It receives and stores
+ *  notifications and sequences of notifications from Siena.
+ *  Notifications and sequences can be retrieved synchronously
+ *  (blocking) or asynchronously (non-blocking).
+ *
+ *  <p>Example:
+ *  <code><pre>
+ *      Siena siena;
+ *      Filter f;
+ *      // ...
+ *      // siena = new ...
+ *      // f = new Filter();
+ *      // f.addConstraint ...
+ *      // ...
+ *      NotificationBuffer queue = new NotificationBuffer();
+ *      siena.subscribe(f, queue);
+ *      Notification n = queue.getNotification(-1); // infinite timeout
+ *      System.out.println(n.toString());
+ *  </pre></code>
+ *
+ *  <p>Notice that <code>NotificationBuffer</code> handles notifications and
+ *  sequences (of notifications) separately.
+ *
+ *  @see Notification
+ *  @see Notifiable
+ **/
+public class NotificationBuffer
+ implements Notifiable {
+    private ObjectBuffer notifications;
+    private ObjectBuffer sequences;
+
+    /** constructs an empty NotificationBuffer with unlimited capacity
+     *
+     *  this NotificationBuffer will grow dynamically to store any number of
+     *  notifications and sequences.
+     **/
+    public NotificationBuffer() {
+	notifications = new DynamicBuffer();
+	sequences = new DynamicBuffer();
+    }
+
+    /** constructs an empty NotificationBuffer with limited capacity
+     *
+     *  this NotificationBuffer will hold up to <em>dimension</em>
+     *  notifications and <em>dimension</em> sequences.  If more
+     *  notifications (or sequences) are received, stored
+     *  notifications (or sequences) will be discarded on a
+     *  first-in-first-out basis.
+     *
+     *  @param dimension maximum capacity.  Must be &gt; 0
+     **/
+    public NotificationBuffer(int dimension) {
+	notifications = new StaticBuffer(dimension);
+	sequences = new StaticBuffer(dimension);
+    }
+
+    /** number of available notifications
+     *
+     *  @return count of available notifications
+     **/
+    public int notificationsCount() {
+	return notifications.size();
+    }
+
+    /** the number of available sequences of notifications
+     *
+     *  @return count of available sequences
+     **/
+    public int sequencesCount() {
+	return sequences.size();
+    }
+
+    public void notify(Notification n) {
+	synchronized (notifications) {
+	    notifications.put(new Notification(n));
+	    notifications.notify();
+	}
+    }
+
+    public void notify(Notification[] s) {
+	synchronized (sequences) {
+	    sequences.put(s);
+	    sequences.notify();
+	}
+    }
+
+    /** attempts to extract a notification (non-blocking)
+     *
+     *  attempts to extract a notification.  This method returns
+     *  immediately.  It returns the first available notification or
+     *  <code>null</code> if none is available.
+     *
+     *  @return first available notification or <code>null</code>
+     **/
+    public Notification getNotification() {
+	synchronized (notifications) {
+	    return (Notification)notifications.get();
+	}
+    }
+
+    /** attempts to extract a notification (blocking)
+     *
+     *  attempts to extract a notification.  This method might block
+     *  if no notification is available.  If the given
+     *  <em>timeout</em> is &gt; 0, this method blocks for at most
+     *  <em>timeout</em> milliseconds, otherwise (when
+     *  <em>timeout</em> &gt;= 0) it blocks until a notification
+     *  becomes available.  It returns the first available
+     *  notification or <code>null</code> in case the timeout expired.
+     *
+     *  @return first available notification or <code>null</code>
+     **/
+    public Notification getNotification(long timeout)
+	throws InterruptedException {
+	synchronized (notifications) {
+	    if (timeout > 0) {
+		if (notifications.isEmpty())
+		    notifications.wait(timeout);
+	    } else {
+		while(notifications.isEmpty())
+		    notifications.wait();
+	    }
+	    return (Notification)notifications.get();
+	}
+    }
+
+    public Notification [] getSequence() throws InterruptedException {
+	synchronized (sequences) {
+	    return (Notification[]) sequences.get();
+	}
+    }
+
+    /** attempts to extract a sequence of notification (non-blocking)
+     *
+     *  attempts to extract a sequence of notifications.  This method
+     *  might block if no notification is available.  If the given
+     *  <em>timeout</em> is &gt; 0, this method blocks for at most
+     *  <em>timeout</em> milliseconds, otherwise (when
+     *  <em>timeout</em> &gt;= 0) it blocks until a notification
+     *  becomes available.  It returns the first available
+     *  notification or <code>null</code> in case the timeout expired.
+     *
+     *  @return first available notification or <code>null</code>
+     **/
+    public Notification [] getSequence(long timeout)
+	throws InterruptedException {
+	synchronized (sequences) {
+	    if (timeout > 0) {
+		if (sequences.isEmpty())
+		    sequences.wait(timeout);
+	    } else {
+		while(sequences.isEmpty())
+		    sequences.wait();
+	    }
+	    return (Notification [])sequences.get();
+	}
+    }
+}
diff --git a/siena/ipaq/Op.java b/siena/ipaq/Op.java
new file mode 100644
index 0000000..9b9db5b
--- /dev/null
+++ b/siena/ipaq/Op.java
@@ -0,0 +1,104 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.lang.Integer;
+import java.lang.String;
+
+/**
+   Siena selection operators.
+
+   Op defines a set of constants that represent the selection
+   operators offered by Siena.  For example, <code>Op.EQ</code>
+   represents the ``equality'' operator, <code>Op.GT</code> represents
+   the ``greater than'' operator, <code>Op.LE</code> represents
+   the ``less or equal'' operator, etc.
+   <p>
+
+   Op also offers a convenient translation function that returns
+   operator codes based on their textual representation.  For example,
+   <code>Op.op("=")</code> returns <code>Op.EQ</code>,
+   <code>Op.op("&gt;")</code> returns <code>Op.GT</code>, ,
+   <code>Op.op("&lt;=")</code> returns <code>Op.LE</code>, etc.
+
+   @see AttributeConstraint
+   @see Filter
+ **/
+public class Op {
+    //
+    // op is one of these:
+    //
+    /** equality operator */
+    public static final short	EQ		= 1;
+    /** less than operator */
+    public static final short	LT		= 2;
+    /** greater than operator */
+    public static final short	GT		= 3;
+    /** greater o equal operator */
+    public static final short	GE		= 4;
+    /** less or equal operator */
+    public static final short	LE		= 5;
+    /** has prefix operator (for strings only, e.g., "software" PF "soft")
+
+	<em>x Op.PF y</em> iff <em>x</em> begins with the prefix <em>y</em>
+    */
+    public static final short	PF		= 6;
+    /** has suffix operator (for strings only, e.g., "software" SF "ware")
+
+	<em>x Op.SF y</em> iff <em>x</em> ends with the suffix <em>y</em>
+     */
+    public static final short	SF		= 7;
+    /** <em>any</em> operator */
+    public static final short	ANY		= 8;
+    /** not equal operator */
+    public static final short	NE		= 9;
+    /** substring operator (for strings only, e.g., "software" SS "war")
+
+	<em>x Op.SS y</em> iff <em>x</em> contains the substring <em>y</em>
+     */
+    public static final short	SS		= 10;
+
+    /** string representation of operators */
+    public static final String	operators[]
+	= { null, "=", "<", ">", ">=", "<=", ">*", "*<", "any", "!=", "*" };
+
+    /**
+       returns the operator corresponding to the given string representation.
+
+       @param strop string representation of the operator (e.g. "=" returns
+              <code>Operator.EQ</code>)
+
+       @return operator code or 0 if the string is not a valid representation
+    **/
+    public static short op(String strop) {
+	for(short i = 1; i < operators.length; ++i)
+	    if (operators[i].equals(strop)) return i;
+	return 0;
+    }
+}
+
diff --git a/siena/ipaq/PacketReceiver.java b/siena/ipaq/PacketReceiver.java
new file mode 100644
index 0000000..df5956f
--- /dev/null
+++ b/siena/ipaq/PacketReceiver.java
@@ -0,0 +1,102 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** abstract packet receiver.
+ *
+ *  <p>Encapsulates a passive acceptor of packets.  This acceptor is
+ *  <em>passive</em> in the sense that it uses the caller's thread to
+ *  accept and assemble the incoming packet.
+ *  <code>PacketReceiver</code>s, together with their corresponding
+ *  <code>PacketSender</code>s, form the communication layer
+ *  underneath the distributed network of Siena components.
+ *
+ *  <p>The implementations of Siena (see {@link
+ *  HierarchicalDispatcher} and {@link ThinClient}) use a
+ *  <code>PacketReceiver</code> as their acceptor of external
+ *  subscriptions, notifications, etc.
+ *
+ *  <p>This version of Siena includes only a simple implementation on
+ *  top of TCP/IP ({@link TCPPacketReceiver}).  Future versions will
+ *  include support for encapsulation into other protocols, such as
+ *  SMTP and HTTP.
+ *
+ *  @see PacketSender
+ **/
+public interface PacketReceiver {
+
+    /** external identifier for this receiver.
+     *
+     *  An external identifier must allow other applications to
+     *  identify and contact this receiver.  Every implementation of
+     *  this interface must agree on a format for external
+     *  identifiers.  The current implementation uses a URL-like
+     *  syntax.
+     *
+     *  <p>Notice also that every implementation of a
+     *  <code>PacketReceiver</code> must have a corresponding
+     *  <code>PacketSender</code>, and that
+     *  <code>PacketSenderFactory</code> must understand its
+     *  <em>uri</em> to construct the corresponding
+     *  <code>PacketSender</code>.
+     *
+     *  @see PacketSender
+     *  @see PacketSenderFactory
+     **/
+    public byte[] uri();
+
+    /** receives a packet in the given buffer.
+     *
+     *  @return the number of bytes read into the buffer.  The return
+     *          value <em>must not be negative</em>.  On error conditions,
+     *          this method must throw an exception.
+     *
+     *  @exception PacketReceiverException in case an error occurrs
+     *		while reading.
+     **/
+    public int receive(byte[] packet) throws PacketReceiverException;
+
+    /** receives a packet in the given buffer, with the given timeout.
+     *
+     *  @return the number of bytes read into the buffer.  The return
+     *          value <em>must not be negative</em>.  On error conditions,
+     *          this method must throw an exception.
+     *
+     *  @exception PacketReceiverException in case an error occurrs
+     *		while reading.
+     *
+     *  @exception TimeoutExpired in case the timout expires before a
+     *		packet is available
+     **/
+    public int receive(byte[] packet, long timeout)
+	throws PacketReceiverException, TimeoutExpired;
+
+    /** closes the receiver.
+     **/
+    public void shutdown() throws PacketReceiverException;
+}
diff --git a/siena/ipaq/PacketReceiverClosed.java b/siena/ipaq/PacketReceiverClosed.java
new file mode 100644
index 0000000..fffd75b
--- /dev/null
+++ b/siena/ipaq/PacketReceiverClosed.java
@@ -0,0 +1,54 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.io.IOException;
+
+/** packet receiver has been closed */
+public class PacketReceiverClosed extends PacketReceiverException {
+    public IOException ioex;
+
+    public PacketReceiverClosed(IOException ex) {
+	super("packet receiver has been closed: " + ex.toString());
+	ioex = ex;
+    }
+
+    public PacketReceiverClosed() {
+	super("packet receiver has been closed");
+	ioex = null;
+    }
+
+    /** IOException that caused this PacketReceiverClosed exception
+     *
+     *  @return IOException that caused this PacketReceiverClosed
+     *          exception or <code>null</code>
+     **/
+    public IOException getIOException() {
+	return ioex;
+    }
+}
diff --git a/siena/ipaq/PacketReceiverException.java b/siena/ipaq/PacketReceiverException.java
new file mode 100644
index 0000000..641a611
--- /dev/null
+++ b/siena/ipaq/PacketReceiverException.java
@@ -0,0 +1,35 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** generic error in sending packets */
+public class PacketReceiverException extends SienaException {
+    public PacketReceiverException(java.lang.String s) {
+	super(s);
+    }
+}
diff --git a/siena/ipaq/PacketReceiverFatalError.java b/siena/ipaq/PacketReceiverFatalError.java
new file mode 100644
index 0000000..c1b599e
--- /dev/null
+++ b/siena/ipaq/PacketReceiverFatalError.java
@@ -0,0 +1,35 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** fatal error in packet receiver */
+public class PacketReceiverFatalError extends PacketReceiverException {
+    public PacketReceiverFatalError(java.lang.String s) {
+	super("fatal error for packet receiver: " + s);
+    }
+}
diff --git a/siena/ipaq/PacketSender.java b/siena/ipaq/PacketSender.java
new file mode 100644
index 0000000..26a69a0
--- /dev/null
+++ b/siena/ipaq/PacketSender.java
@@ -0,0 +1,58 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** packet sender.
+ *
+ *  Abstraction of a primitive communication mechanism for sending
+ *  packets.  Packets are chunks of bytes.  A packet sender sends
+ *  packets to a specific destination.  Every implementation of Siena
+ *  uses one or more packet senders to communicate with remote clients
+ *  and servers.
+ *
+ *  <p>Packet senders are constructed by {@link PacketSenderFactory}
+ *  using the URI of the corresponding receiver as a parameter.
+ *
+ *  <p>This version of Siena includes only a simple implementation on
+ *  top of TCP/IP.  Future versions will include support for
+ *  encapsulation into other protocols, such as SMTP and HTTP.
+ *
+ *  @see PacketReceiver
+ **/
+public interface PacketSender {
+    /** sends a packet. **/
+    public void		send(byte[] packet) throws PacketSenderException;
+
+    /** sends a packet. **/
+    public void		send(byte[] packet, int len)
+	throws PacketSenderException;
+
+    /** sends a packet. **/
+    public void		send(byte[] packet, int offset, int len)
+	throws PacketSenderException;
+}
diff --git a/siena/ipaq/PacketSenderException.java b/siena/ipaq/PacketSenderException.java
new file mode 100644
index 0000000..700b8b1
--- /dev/null
+++ b/siena/ipaq/PacketSenderException.java
@@ -0,0 +1,35 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** generic error in sending packets */
+public class PacketSenderException extends SienaException {
+    public PacketSenderException(java.lang.String s) {
+	super(s);
+    }
+}
diff --git a/siena/ipaq/PacketSenderFactory.java b/siena/ipaq/PacketSenderFactory.java
new file mode 100644
index 0000000..e248069
--- /dev/null
+++ b/siena/ipaq/PacketSenderFactory.java
@@ -0,0 +1,42 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.io.IOException;
+
+/** creates PacketSenders based on their URI unwieldy
+ *
+ *  creates PacketSender objects based on their external (URI)
+ *  representation.  In other words, given the {@link
+ *  PacketReceiver#uri() uri of a receiver}, returns a PacketSender
+ *  object that can contact that receiver.
+ **/
+public interface PacketSenderFactory {
+    public PacketSender createPacketSender(String handler)
+	throws InvalidSenderException;
+}
diff --git a/siena/ipaq/Pattern.java b/siena/ipaq/Pattern.java
new file mode 100644
index 0000000..5297007
--- /dev/null
+++ b/siena/ipaq/Pattern.java
@@ -0,0 +1,71 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** a selection for a sequence of <code>Notification</code>s.
+
+    A <code>Pattern</code> is a sequence of <code>Filter</code>s,
+    matched by a sequence of notifications, each one matching the
+    corresponding filter in the <code>Pattern</code>.  <p>
+
+    For example a pattern <em>[file = "hosts"]; [file = "passwd"]</em>
+    is matched by two events <em>e<sub>1</sub></em> and
+    <em>e<sub>2</sub></em> such that <em>e<sub>1</sub></em> matches
+    filter <em>[file = "hosts"]</em>, and <em>e<sub>1</sub></em> is
+    followed by <em>e<sub>2</sub></em>, and <em>e<sub>2</sub></em>
+    matches filter <em>[file = "passwd"].  <p>
+
+    @see AttributeConstraint
+    @see Filter
+    @see Notification
+    @author Antonio Carzaniga
+**/
+public class Pattern {
+    //
+    // this should be private...
+    //
+    public Filter	filters[] = null;
+
+    /** creates a pattern with the given array of filters.
+     */
+    public Pattern(Filter p[]) {
+	filters = new Filter[p.length];
+	int i = p.length;
+	while(--i >= 0)
+	    filters[i] = p[i];
+    }
+
+    /** creates a (deep) copy of a given pattern.
+     */
+    public Pattern(Pattern p) {
+	filters = new Filter[p.filters.length];
+	int i = p.filters.length;
+	while(--i >= 0)
+	    filters[i] = new Filter(p.filters[i]);
+    }
+}
diff --git a/siena/ipaq/SENP.java b/siena/ipaq/SENP.java
new file mode 100644
index 0000000..c656b74
--- /dev/null
+++ b/siena/ipaq/SENP.java
@@ -0,0 +1,147 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import com.sun.java.util.collections.ListIterator;
+import com.sun.java.util.collections.LinkedList;
+import com.sun.java.util.collections.Iterator;
+
+
+public class SENP {
+    public static final byte ProtocolVersion = 1;
+
+    public static final byte[] Version
+    = {0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e};	// version
+    public static final byte[] To
+    = {0x74, 0x6F};					// to
+    public static final byte[] Method
+    = {0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64};		// method
+    public static final byte[] Id
+    = {0x69, 0x64};					// id
+    public static final byte[] Handler
+    = {0x68, 0x61, 0x6e, 0x64, 0x6c, 0x65, 0x72};	// handler
+    public static final byte[] Ttl
+    = {0x74, 0x74, 0x6c};				// ttl
+
+    public static final int	DefaultTtl		= 30;
+
+    public static final int	MaxPacketLen		= 65536;
+
+    public static final byte NOP = 0;
+    public static final byte PUB = 1;
+    public static final byte SUB = 2;
+    public static final byte UNS = 3;
+    public static final byte ADV = 4;
+    public static final byte UNA = 5;
+    public static final byte HLO = 6;
+    public static final byte BYE = 7;
+    public static final byte SUS = 8;
+    public static final byte RES = 9;
+    public static final byte MAP = 10;
+    public static final byte WHO = 11;
+    public static final byte INF = 12;
+    public static final byte CNF = 13;
+    public static final byte OFF = 14;
+
+    public static final byte[][] Methods =
+    {
+	{ 0x4E, 0x4F, 0x50 },	// NOP
+	{ 0x50, 0x55, 0x42 },	// PUB
+	{ 0x53, 0x55, 0x42 },	// SUB
+	{ 0x55, 0x4E, 0x53 },	// UNS
+	{ 0x41, 0x44, 0x56 },	// ADV
+	{ 0x55, 0x4E, 0x41 },	// UNA
+	{ 0x48, 0x4C, 0x4F },	// HLO
+	{ 0x42, 0x59, 0x45 },	// BYE
+	{ 0x53, 0x55, 0x53 },	// SUS
+	{ 0x52, 0x45, 0x53 },	// RES
+	{ 0x4D, 0x41, 0x50 },	// MAP
+	{ 0x57, 0x48, 0x4f },	// WHO
+	{ 0x49, 0x4e, 0x46 },	// INF
+	{ 0x43, 0x4e, 0x46 },	// CNF
+	{ 0x4F, 0x46, 0x46 }	// OFF
+    };
+
+    //
+    //  WARNING:  don't mess up the order of operators in this array
+    //            it must correspond to the definitions of
+    //            Op.EQ, Op.LT, etc.
+    //
+    public static final byte[][] operators = {
+	{0x3f}, // ?
+	{0x3d}, // "="
+	{0x3c}, // "<"
+	{0x3e}, // ">"
+	{0x3e, 0x3d}, // ">="
+	{0x3c, 0x3d}, // "<="
+	{0x3e, 0x2a}, // ">*"
+	{0x2a, 0x3c}, // "*<"
+	{0x61, 0x6e, 0x79}, // any,
+	{0x21, 0x3d}, // "!="
+	{0x2a} // "*"
+    };
+    //
+    // default port numbers
+    //
+    public static final int	CLIENT_PORT		= 1936;
+    public static final int	SERVER_PORT		= 1969;
+    public static final int	DEFAULT_PORT		= 1969;
+
+    public static final byte[] KwdSeparator = { 0x20 }; // ' '
+    public static final byte[] KwdSenp
+    = {0x73, 0x65, 0x6e, 0x70}; // senp
+    public static final byte[] KwdEvent
+    = {0x65, 0x76, 0x65, 0x6e, 0x74}; // event
+    public static final byte[] KwdEvents
+    = {0x65, 0x76, 0x65, 0x6e, 0x74, 0x73}; // events
+    public static final byte[] KwdFilter
+    = {0x66, 0x69, 0x6c, 0x74, 0x65, 0x72}; // filter
+    public static final byte[] KwdPattern
+    = {0x70, 0x61, 0x74, 0x74, 0x65, 0x72, 0x6e}; // pattern
+    public static final byte[] KwdLParen
+    = {0x7b}; // {
+    public static final byte[] KwdRParen
+    = {0x7d}; // }
+    public static final byte[] KwdEquals
+    = {0x3d}; // =
+    public static final byte[] KwdTrue
+    = {0x74, 0x72, 0x75, 0x65}; // true
+    public static final byte[] KwdFalse
+    = {0x66, 0x61, 0x6c, 0x73, 0x65}; // false
+
+    public static final byte[] KwdNull
+    = {0x6e, 0x75, 0x6c, 0x6c}; // null
+
+    public static boolean match(byte [] x, byte [] y) {
+        if (x == null && y == null) return true;
+        if (x == null || y == null || x.length != y.length) return false;
+        for(int i = 0; i < x.length; ++i)
+            if (x[i] != y[i]) return false;
+        return true;
+    }
+}
diff --git a/siena/ipaq/SENPInvalidFormat.java b/siena/ipaq/SENPInvalidFormat.java
new file mode 100644
index 0000000..8a037ad
--- /dev/null
+++ b/siena/ipaq/SENPInvalidFormat.java
@@ -0,0 +1,63 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** malformed SENP packet **/
+public class SENPInvalidFormat extends SienaException {
+    int		expected_type;
+    String	expected_value;
+    int		line_number;
+
+    public SENPInvalidFormat() {
+	super();
+    }
+    public SENPInvalidFormat(String v) {
+	super("expecting: `" + v + "'");
+	expected_value = v;
+    }
+    public SENPInvalidFormat(int t, String v) {
+	this(v);
+	expected_type = t;
+    }
+    public SENPInvalidFormat(int t) {
+	this();
+	expected_type = t;
+    }
+
+    public int getExpectedType() {
+	return expected_type;
+    }
+
+    public String getExpectedValue() {
+	return expected_value;
+    }
+
+    public int getLineNumber() {
+	return expected_type;
+    }
+}
diff --git a/siena/ipaq/SENPPacket.java b/siena/ipaq/SENPPacket.java
new file mode 100644
index 0000000..900fad5
--- /dev/null
+++ b/siena/ipaq/SENPPacket.java
@@ -0,0 +1,882 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import com.sun.java.util.collections.Iterator;
+import com.sun.java.util.collections.LinkedList;
+import com.sun.java.util.collections.ListIterator;
+
+class SENPBuffer {
+    private byte []	sval_buf;
+    public byte[]	buf;
+    protected int	pos;
+    protected int	last;
+
+    public SENPBuffer() {
+	buf = new byte[SENP.MaxPacketLen];
+	sval_buf = new byte[SENP.MaxPacketLen];
+	pos = 0;
+	last = 0;
+	sval_last = 0;
+    }
+
+    public void init() {
+	pos = 0;
+	last = 0;
+	sval_last = 0;
+    }
+
+    public void init(int len) {
+	pos = 0;
+	last = len;
+	sval_last = 0;
+    }
+
+    public void init(byte[] b) {
+	pos = 0;
+	last = 0;
+	sval_last = 0;
+	append(b);
+    }
+
+    public void append(byte b) {
+	buf[pos++] = b;
+    }
+
+    public void append(int x) {
+	buf[pos++] = (byte)x;
+    }
+
+    public void append(byte[] bytes) {
+	for(int i = 0; i < bytes.length; ++i)
+	    buf[pos++] = bytes[i];
+    }
+
+    public void append(String s) {
+	append(s.getBytes());
+    }
+
+    public int length() {
+	return last;
+    }
+
+    //
+    // WARNING: now, since Java doesn't have byte literals in the form
+    // of ascii characters like good old 'a' '*' '\n' etc.  I'll have
+    // to use the corresponding decimal values.  Which is the
+    // Right(tm) way of doing that, according to some clowns out
+    // there...
+    //
+
+    //
+    // token types
+    //
+    public static final int	T_EOF		= -1;
+    public static final int	T_UNKNOWN	= -2;
+    //
+    // keywords
+    //
+    public static final int	T_ID		= -3;
+    public static final int	T_STR		= -4;
+    public static final int	T_INT		= -5;
+    public static final int	T_DOUBLE	= -6;
+    public static final int	T_BOOL		= -7;
+    public static final int	T_OP		= -8;
+    public static final int	T_LPAREN	= -9;
+    public static final int	T_RPAREN	= -10;
+
+    public short	oval;
+    public long		ival;
+    public boolean	bval;
+    public double	dval;
+
+    private int		sval_last = 0;
+
+    private int nextByte() {
+	if (++pos >= last) return -1;
+	return buf[pos];
+    }
+
+    private int currByte() {
+	if (pos >= last) return -1;
+	return buf[pos];
+    }
+
+    private void pushBack() {
+	if (pos > 0) pos--;
+    }
+
+    private boolean isCurrentFirstIdentChar() {
+	if (pos >= last) return false;
+	return (buf[pos] >= 0x41 && buf[pos] <= 0x5a)	// 'A' -- 'Z'
+	    || (buf[pos] >= 0x61 && buf[pos] <= 0x7a)	// 'a' -- 'z'
+	    || buf[pos] == 0x5f;			// '_'
+    }
+
+    private boolean isCurrentIdentChar() {
+	if (pos >= last) return false;
+	return (buf[pos] >= 0x41 && buf[pos] <= 0x5a)	// 'A' -- 'Z'
+	    || (buf[pos] >= 0x61 && buf[pos] <= 0x7a)	// 'a' -- 'z'
+	    || (buf[pos] >= 0x30 && buf[pos] <= 0x39)	// '0' -- '9'
+	    || buf[pos] == 0x5f || buf[pos] == 0x24	// '_', '$'
+	    || buf[pos] == 0x2e || buf[pos] == 0x2f;	// '.', '/'
+    }
+
+    byte read_octal() {
+				/* '0' -- '7' */
+	byte nb = 0;
+	int i = 3;
+	do {
+	    nb = (byte)(nb * 8 + currByte() - 0x30);
+	} while(--i > 0 && ++pos < last
+		&& buf[pos] >= 0x30 && buf[pos] <= 0x37);
+	return nb;
+    }
+
+    int read_string() {
+	//
+	// here buf[pos] == '"'
+	//
+	sval_last = 0;
+	while(++pos < last)
+	    switch (buf[pos]) {
+	    case 0x22 /* '"' */: ++pos; return T_STR;
+	    case 0x5c /* '\\' */:
+		if (++pos >= last) return T_UNKNOWN;
+		switch (buf[pos]) {
+		case 0x76 /* 'v' */:
+		    sval_buf[sval_last++] = 0x0b /* '\v' */;break;
+		case 0x66 /* 'f' */:
+		    sval_buf[sval_last++] = 0x0c /* '\f' */;break;
+		case 0x72 /* 'r' */:
+		    sval_buf[sval_last++] = 0x0d /* '\r' */;break;
+		case 0x6e /* 'n' */:
+		    sval_buf[sval_last++] = 0x0a /* '\n' */; break;
+		case 0x74 /* 't' */:
+		    sval_buf[sval_last++] = 0x09 /* '\t' */; break;
+		case 0x62 /* 'b' */:
+		    sval_buf[sval_last++] = 0x08 /* '\b' */; break;
+		case 0x61 /* 'a' */:
+		    sval_buf[sval_last++] = 0x07 /* '\a' */; break;
+		default:
+		    if (buf[pos] >= 0x30 && buf[pos] <= 0x37) {
+			sval_buf[sval_last++] = read_octal();
+		    } else {
+			sval_buf[sval_last++] = buf[pos];
+		    }
+		}
+		break;
+	    default:
+		sval_buf[sval_last++] = buf[pos];
+	    }
+	return T_UNKNOWN;
+    }
+
+    int read_id() {
+	sval_last = 0;
+	do {
+	    sval_buf[sval_last++] = buf[pos++];
+	} while(isCurrentIdentChar());
+	return T_ID;
+    }
+
+    int read_int() {
+	boolean negative = false;
+	//
+	// here buf[pos] is either a digit or '-'
+	//
+	if (buf[pos] == 0x2d /* '-' */) {
+	    negative = true;
+	    ival = 0;
+	    if (++pos >= last || buf[pos] < 0x30 || buf[pos] > 0x39)
+		return T_UNKNOWN;
+	} else {
+	    ival = buf[pos] - 0x30;
+	    if (++pos >= last || buf[pos] < 0x30 || buf[pos] > 0x39)
+		return T_INT;
+	}
+	do {
+	    ival = ival * 10 + buf[pos] - 0x30;
+	} while (++pos < last && buf[pos] >= 0x30 && buf[pos] <= 0x39);
+	if (negative) ival = -ival;
+	return T_INT;
+    }
+
+    int read_number() {
+	boolean negative = false;
+	//
+	// here buf[pos] is either a digit or '-'
+	//
+	if (buf[pos] == 0x2d /* '-' */) {
+	    negative = true;
+	    if (++pos >= last || buf[pos] < 0x30 || buf[pos] > 0x39)
+		return T_UNKNOWN;
+	}
+	int type;
+	if (read_int() == T_UNKNOWN) return T_UNKNOWN;
+	type = T_INT;
+	dval = ival;
+	if (pos < last && buf[pos] == 0x2e /* '.' */) {
+	    type = T_DOUBLE;
+	    if (++pos >= last || buf[pos] < 0x30 || buf[pos] > 0x39) {
+		return T_UNKNOWN;
+	    } else {
+		dval += read_decimal();
+	    }
+	}
+	if (pos < last)
+	    if (buf[pos] == 101 /* 'e' */ || buf[pos] == 69) /* 'E' */ {
+		type = T_DOUBLE;
+		if (++pos >= last
+		    || ((buf[pos] < 0x30 || buf[pos] > 0x39)
+			&& buf[pos] != 0x2d /* '-' */))
+		    return T_UNKNOWN;
+		if (read_int() == T_UNKNOWN) return T_UNKNOWN;
+		dval *= java.lang.Math.pow(10,ival);
+	    }
+	if (negative) {
+	    if (type == T_INT) {
+		ival = -ival;
+	    } else {
+		dval = -dval;
+	    }
+	}
+	return type;
+    }
+
+    double read_decimal() {
+	//
+	// here buf[pos] is a digit
+	//
+	long intpart = 0;
+	long divisor = 1;
+	do {
+	    intpart = intpart*10 + (buf[pos] - 0x30);
+	    divisor *= 10;
+	} while(++pos < last && buf[pos] >= 0x30 && buf[pos] <= 0x39);
+	return (1.0 * intpart) / divisor;
+    }
+
+    public int nextToken() {
+	while (true) {
+	    switch(currByte()) {
+	    case -1: return T_EOF;
+	    case 0x22 /* '"' */: return read_string();
+	    case 123 /* '{' */: ++pos; return T_LPAREN;
+	    case 125 /* '}' */: ++pos; return T_RPAREN;
+	    case 33 /* '!' */:
+		switch(nextByte()) {
+		case 0x3d /* '=' */:
+		    oval = Op.NE; ++pos; return T_OP;
+		default: return T_UNKNOWN;
+		}
+	    case 42 /* '*' */:
+		switch(nextByte()) {
+		case 60 /* '<' */:
+		    oval = Op.SF; ++pos; return T_OP;
+		default:
+		    oval = Op.SS; return T_OP;
+		}
+	    case 0x3d /* '=' */:
+		oval = Op.EQ; ++pos; return T_OP;
+	    case 62 /* '>' */:
+		switch(nextByte()) {
+		case 42 /* '*' */:
+		    oval = Op.PF; ++pos; return T_OP;
+		case 0x3d /* '=' */:
+		    oval = Op.GE; ++pos; return T_OP;
+		default:
+		    oval = Op.GT; return T_OP;
+		}
+	    case 60 /* '<' */:
+		switch(nextByte()) {
+		case 0x3d /* '=' */:
+		    oval = Op.LE; ++pos; return T_OP;
+		default:
+		    oval = Op.LT; return T_OP;
+		}
+	    default:
+		if ((buf[pos] >= 0x30 && buf[pos] <= 0x39) /* '0' -- '9' */
+		     || buf[pos] == 0x2d /* '-' */) {
+		    return read_number();
+		} else if (isCurrentFirstIdentChar()) {
+		    return read_id();
+		} else {
+		    //
+		    // I simply ignore characters that I don't understand
+		    //
+		    ++pos;
+		}
+	    }
+	}
+    }
+
+    public boolean match_sval(byte[] y) {
+	if (sval_last == 0 && y == null) return true;
+	if (sval_last == 0 || y == null || sval_last != y.length) return false;
+	for(int i = 0; i < sval_last; ++i)
+	    if (sval_buf[i] != y[i]) return false;
+	return true;
+    }
+
+    public byte[] copy_sval() {
+	byte [] res = new byte[sval_last];
+	for(int i = 0; i < sval_last; ++i)
+	    res[i] = sval_buf[i];
+	return res;
+    }
+
+    public String sval_string() {
+	return new String(sval_buf, 0, sval_last);
+    }
+
+    protected void encode_octal(byte x) {
+	buf[pos++] = (byte)(((x >> 6) & 3) + 0x30);
+	buf[pos++] = (byte)(((x >> 3) & 7) + 0x30);
+	buf[pos++] = (byte)((x & 7) + 0x30);
+    }
+
+    protected void encode_decimal(long x) {
+	byte[] tmp = new byte[20]; // Log(MAX_LONG)+1
+	int p = 0;
+	boolean negative = (x<0);
+	if (negative) x = -x;
+
+	do {
+	    tmp[p++] = (byte)(x % 10 + 0x30 /* '0' */);
+	    x /= 10;
+	} while (x > 0);
+	if (negative) buf[pos++] = 0x2d; /* '-' */
+	while(p-- > 0) buf[pos++] = tmp[p];
+    }
+
+    void encode(byte[] bv) {
+	buf[pos++] = 0x22 /* '"' */;
+	for(int i = 0; i < bv.length; ++i) {
+	    switch(bv[i]) {
+	    case 11 /* '\v' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x76 /* 'v' */;
+		break;
+	    case 12 /* '\f' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x66 /* 'f' */;
+		break;
+	    case 13 /* '\r' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x72 /* 'r' */;
+		break;
+	    case 10 /* '\n' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x6e /* 'n' */;
+		break;
+	    case 9 /* '\t' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x74 /* 't' */;
+		break;
+	    case 8 /* '\b' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x62 /* 'b' */;
+		break;
+	    case 7 /* '\a' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x61 /* 'a' */;
+		break;
+	    case 0x22 /* '"' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x22 /* '"' */;
+		break;
+	    case 0x5c /* '\\' */:
+		buf[pos++] = 0x5c /* '\\' */;
+		buf[pos++] = 0x5c /* '\\' */;
+		break;
+	    default:
+		if (bv[i] < 0x20 || bv[i] >= 0x7F) {
+		    //
+		    // here I handle other non-printable characters with
+		    // the \xxx octal notation ...work in progress...
+		    //
+		    buf[pos++] = 0x5c;
+		    encode_octal(bv[i]);
+		} else {
+		    buf[pos++] = bv[i];
+		}
+	    }
+	}
+	buf[pos++] = 0x22 /* '"' */;
+	if (pos > last) last = pos;
+    }
+
+    public void encode(AttributeValue a) {
+	switch(a.getType()) {
+	case AttributeValue.LONG:
+	    encode_decimal(a.longValue());
+	    break;
+	case AttributeValue.BOOL:
+	    append(a.booleanValue() ? SENP.KwdTrue : SENP.KwdFalse);
+	    break;
+	case AttributeValue.DOUBLE:
+	    append(Double.toString(a.doubleValue()));
+	    break;
+	case AttributeValue.BYTEARRAY:
+	    encode(a.byteArrayValue());
+	    break;
+	case AttributeValue.NULL:
+	    append(SENP.KwdNull);
+	    break;
+	default:
+	    // should throw an exception here
+	    // ...work in progress...
+	}
+	if (pos > last) last = pos;
+    }
+
+    public void encode(Notification e) {
+	append(SENP.KwdEvent);
+	append(SENP.KwdLParen);
+	Iterator i = e.attributeNamesIterator();
+	while(i.hasNext()) {
+	    append(SENP.KwdSeparator);
+	    String name = (String)i.next();
+	    append(name);
+	    append(SENP.KwdEquals);
+	    encode(e.getAttribute(name));
+	}
+	append(SENP.KwdRParen);
+	if (pos > last) last = pos;
+    }
+
+    public void encode(Pattern p) {
+	append(SENP.KwdPattern);
+	append(SENP.KwdLParen);
+	for(int i = 0; i < p.filters.length; ++i) {
+	    append(SENP.KwdSeparator);
+	    encode(p.filters[i]);
+	}
+	append(SENP.KwdRParen);
+	if (pos > last) last = pos;
+    }
+
+    public void encode(Notification[] s) {
+	append(SENP.KwdEvents);
+	append(SENP.KwdLParen);
+	for(int i = 0; i < s.length; ++i) {
+	    append(SENP.KwdSeparator);
+	    encode(s[i]);
+	}
+	append(SENP.KwdRParen);
+	if (pos > last) last = pos;
+    }
+
+    public void encode(Filter f) {
+	append(SENP.KwdFilter);
+	append(SENP.KwdLParen);
+	Iterator i = f.constraintNamesIterator();
+	while(i.hasNext()) {
+	    String name = (String)i.next();
+	    Iterator j = f.constraintsIterator(name);
+	    while(j.hasNext()) {
+		append(SENP.KwdSeparator);
+		append(name + " ");
+		encode((AttributeConstraint)j.next());
+	    }
+	}
+	append(SENP.KwdRParen);
+	if (pos > last) last = pos;
+    }
+
+
+    public void encode(AttributeConstraint a) {
+	append(SENP.operators[a.op]);
+	if (a.op == Op.ANY) return;
+	encode(a.value);
+	if (pos > last) last = pos;
+    }
+
+    AttributeValue decodeAttribute() throws SENPInvalidFormat {
+	switch(nextToken()) {
+	case T_ID:
+	    if (match_sval(SENP.KwdTrue)) return new AttributeValue(true);
+	    if (match_sval(SENP.KwdFalse)) return new AttributeValue(false);
+	    if (match_sval(SENP.KwdNull)) return new AttributeValue();
+	    return new AttributeValue(copy_sval());
+	case T_STR: return new AttributeValue(copy_sval());
+	case T_INT: return new AttributeValue(ival);
+	case T_BOOL: return new AttributeValue(bval);
+	case T_DOUBLE: return new AttributeValue(dval);
+	default:
+	    throw(new SENPInvalidFormat("<int>, <string>, <bool> or <double>"));
+	}
+    }
+
+    static final String ErrAttrName = "<attribute-name>";
+    static final String ErrParam
+	= "`event' or `filter' or `pattern' or `events'";
+    static final String ErrEvent = "`event'";
+    static final String ErrFilter = "`filter'";
+
+    AttributeConstraint decodeAttributeConstraint()
+	throws SENPInvalidFormat {
+	switch (nextToken()) {
+	case T_ID:
+	    if (match_sval(SENP.operators[Op.ANY])) {
+		return new AttributeConstraint(Op.ANY, (AttributeValue)null);
+	    } else {
+		throw(new SENPInvalidFormat(T_OP));
+	    }
+	case T_OP: {
+	    short op = oval;
+	    return new AttributeConstraint(op, decodeAttribute());
+	}
+	default:
+	    throw(new SENPInvalidFormat(T_OP));
+	}
+    }
+
+    Notification decodeNotification()
+	throws SENPInvalidFormat {
+	if (nextToken() != T_LPAREN)
+	    throw(new SENPInvalidFormat(T_LPAREN, new String(SENP.KwdLParen)));
+	int ttype;
+	Notification e = new Notification();
+	while ((ttype = nextToken()) != T_RPAREN) {
+	    if (ttype != T_ID && ttype != T_STR)
+		throw(new SENPInvalidFormat(T_ID, ErrAttrName));
+	    String name = sval_string();
+	    if (nextToken() != T_OP || oval != Op.EQ)
+		throw(new SENPInvalidFormat(T_OP, new String(SENP.KwdEquals)));
+	    e.putAttribute(name, decodeAttribute());
+	}
+	return e;
+    }
+
+    Notification [] decodeNotifications()
+	throws SENPInvalidFormat {
+	if (nextToken() != T_LPAREN)
+	    throw(new SENPInvalidFormat(T_LPAREN, new String(SENP.KwdLParen)));
+	LinkedList l = new LinkedList();
+	int ttype;
+
+	while ((ttype = nextToken()) != T_RPAREN) {
+	    if (ttype != T_ID && !match_sval(SENP.KwdEvent))
+		throw(new SENPInvalidFormat(T_ID, ErrEvent));
+	    l.addLast(decodeNotification());
+	}
+	Notification [] res = new Notification[l.size()];
+	int i = 0;
+	for(ListIterator li = l.listIterator(); li.hasNext(); ++i)
+	    res[i] = (Notification)li.next();
+	return res;
+    }
+
+    Pattern decodePattern()
+	throws SENPInvalidFormat {
+	if (nextToken() != T_LPAREN)
+	    throw(new SENPInvalidFormat(T_LPAREN, new String(SENP.KwdLParen)));
+	LinkedList l = new LinkedList();
+	int ttype;
+
+	while ((ttype = nextToken()) != T_RPAREN) {
+	    if (ttype != T_ID && ttype != T_STR
+		&& !match_sval(SENP.KwdFilter))
+		throw(new SENPInvalidFormat(T_ID, ErrFilter));
+	    l.addLast(decodeFilter());
+	}
+	Filter [] ff = new Filter[l.size()];
+	int i = 0;
+	for(ListIterator li = l.listIterator(); li.hasNext(); ++i)
+	    ff[i] = (Filter)li.next();
+	return new Pattern(ff);
+    }
+
+    Filter decodeFilter() throws SENPInvalidFormat {
+	if (nextToken() != T_LPAREN)
+	    throw(new SENPInvalidFormat(T_LPAREN, new String(SENP.KwdLParen)));
+	Filter f = new Filter();
+	int ttype;
+	while ((ttype = nextToken()) != T_RPAREN) {
+	    if (ttype != T_ID && ttype != T_STR)
+		throw(new SENPInvalidFormat(T_ID, ErrAttrName));
+
+	    f.addConstraint(sval_string(), decodeAttributeConstraint());
+	}
+	return f;
+    }
+}
+
+class SENPPacket extends SENPBuffer {
+    private SENPPacket		next;
+
+    public byte			version;
+    public byte			method;
+    public byte			ttl;
+    public byte[]		to;
+    public byte[]		id;
+    public byte[]		handler;
+
+    public Notification		event;
+    public Filter		filter;
+    public Pattern		pattern;
+    public Notification[]	events;
+
+    public SENPPacket() {
+	super();
+	init();
+    }
+
+    public void init(int len) {
+	pos = 0;
+	last = len;
+
+	next = null;
+	version = SENP.ProtocolVersion;
+	method = SENP.NOP;
+	ttl = SENP.DefaultTtl;
+	to = null;
+	id = null;
+	handler = null;
+
+	event = null;
+	filter = null;
+	pattern = null;
+	events = null;
+    }
+
+    public void init(byte[] b) {
+	pos = 0;
+	last = b.length;
+
+	append(b);
+
+	next = null;
+	version = SENP.ProtocolVersion;
+	method = SENP.NOP;
+	ttl = SENP.DefaultTtl;
+	to = null;
+	id = null;
+	handler = null;
+
+	event = null;
+	filter = null;
+	pattern = null;
+	events = null;
+    }
+
+    public void init() {
+	pos = 0;
+	last = 0;
+
+	next = null;
+	version = SENP.ProtocolVersion;
+	method = SENP.NOP;
+	ttl = SENP.DefaultTtl;
+	to = null;
+	id = null;
+	handler = null;
+
+	event = null;
+	filter = null;
+	pattern = null;
+	events = null;
+    }
+
+    public String toString() {
+	encode();
+	return new String(buf, 0, length());
+    }
+
+    static private SENPPacket packet_cache = null;
+
+    synchronized static public SENPPacket allocate() {
+	SENPPacket res;
+	if (packet_cache == null) {
+	    res = new SENPPacket();
+	} else {
+	    res = packet_cache;
+	    packet_cache = packet_cache.next;
+	    res.init();
+	}
+	return res;
+    }
+
+    synchronized static public void recycle(SENPPacket p) {
+	p.next = packet_cache;
+	packet_cache = p;
+    }
+
+    public int encode() {
+	pos = 0;
+	last = 0;
+	append(SENP.KwdSenp);
+	append(SENP.KwdLParen);
+
+	append(SENP.Version);
+	append(SENP.KwdEquals);
+	encode_decimal(version);
+	append(SENP.KwdSeparator);
+
+	append(SENP.Method);
+	append(SENP.KwdEquals);
+	encode(SENP.Methods[method]);
+	append(SENP.KwdSeparator);
+
+	append(SENP.Ttl);
+	append(SENP.KwdEquals);
+	encode_decimal(ttl);
+
+	if (id != null) {
+	    append(SENP.KwdSeparator);
+	    append(SENP.Id);
+	    append(SENP.KwdEquals);
+	    encode(id);
+	}
+
+	if (to != null) {
+	    append(SENP.KwdSeparator);
+	    append(SENP.To);
+	    append(SENP.KwdEquals);
+	    encode(to);
+	}
+
+	if (handler != null) {
+	    append(SENP.KwdSeparator);
+	    append(SENP.Handler);
+	    append(SENP.KwdEquals);
+	    encode(handler);
+	}
+
+	append(SENP.KwdRParen);
+
+	if (event != null) {
+	    append(SENP.KwdSeparator);
+	    encode(event);
+	} else if (filter != null) {
+	    append(SENP.KwdSeparator);
+	    encode(filter);
+	} else if (pattern != null) {
+	    append(SENP.KwdSeparator);
+	    encode(pattern);
+	} else if (events != null) {
+	    append(SENP.KwdSeparator);
+	    encode(events);
+	}
+	if (pos > last) last = pos;
+	return last;
+    }
+
+    public void decode() throws SENPInvalidFormat {
+	int ttype;
+
+	pos = 0;
+	if (nextToken() != T_ID || !match_sval(SENP.KwdSenp))
+	    throw(new SENPInvalidFormat(T_ID, new String(SENP.KwdSenp)));
+
+	if (nextToken() != T_LPAREN)
+	    throw(new SENPInvalidFormat(T_LPAREN, new String(SENP.KwdLParen)));
+
+	String name;
+
+	while ((ttype = nextToken()) != T_RPAREN) {
+	    if (ttype != T_ID)
+		throw(new SENPInvalidFormat(T_ID, ErrAttrName));
+
+	    if (nextToken() != T_OP || oval != Op.EQ)
+		throw(new SENPInvalidFormat(T_OP, new String(SENP.KwdEquals)));
+
+	    if (match_sval(SENP.Method)) {
+		switch(nextToken()) {
+		case T_ID:
+		case T_STR:
+		    for (byte mi = 0; mi < SENP.Methods.length; ++mi)
+			if (match_sval(SENP.Methods[mi])) {
+			    method = mi;
+			    break;
+			}
+		    break;
+		default:
+		    throw(new SENPInvalidFormat(T_ID, "expecting method"));
+		}
+	    } else if (match_sval(SENP.Ttl)) {
+		if (nextToken() == T_INT) {
+		    ttl = (byte)ival;
+		} else {
+		    throw(new SENPInvalidFormat(T_INT, "expecting ttl value"));
+		}
+	    } else if (match_sval(SENP.Version)) {
+		if (nextToken() == T_INT) {
+		    version = (byte)ival;
+		} else {
+		    throw(new SENPInvalidFormat(T_INT,
+						"expecting version value"));
+		}
+	    } else if (match_sval(SENP.Id)) {
+		if (nextToken() == T_STR) {
+		    id = copy_sval();
+		} else {
+		    throw(new SENPInvalidFormat(T_STR, "expecting id value"));
+		}
+	    } else if (match_sval(SENP.To)) {
+		if (nextToken() == T_STR) {
+		    to = copy_sval();
+		} else {
+		    throw(new SENPInvalidFormat(T_STR, "expecting to value"));
+		}
+	    } else if (match_sval(SENP.Handler)) {
+		if (nextToken() == T_STR) {
+		    handler = copy_sval();
+		} else {
+		    throw(new SENPInvalidFormat(T_STR,
+						"expecting handler value"));
+		}
+	    } else {
+		    throw(new SENPInvalidFormat(T_STR, "unknown header field "
+						+ sval_string()));
+	    }
+	}
+	//
+	// now reads the optional parameter: either a filter or an event
+	//
+	switch (nextToken()) {
+	case T_EOF: return;
+	case T_ID:
+	    if (match_sval(SENP.KwdFilter)) {
+		filter = decodeFilter();
+	    } else if (match_sval(SENP.KwdEvent)) {
+		event = decodeNotification();
+	    } else if (match_sval(SENP.KwdEvents)) {
+		events = decodeNotifications();
+	    } else if (match_sval(SENP.KwdPattern)) {
+		pattern = decodePattern();
+	    }
+	    return;
+	default:
+	    throw(new SENPInvalidFormat(ErrParam));
+	}
+    }
+}
+
diff --git a/siena/ipaq/Siena.java b/siena/ipaq/Siena.java
new file mode 100644
index 0000000..fc91df0
--- /dev/null
+++ b/siena/ipaq/Siena.java
@@ -0,0 +1,188 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** interface of the <em><b>Siena</b></em> event notification service.
+ *
+ *  implementations of this interface are access points to the Siena
+ *  service.  Applications use them to publish, subscribe,
+ *  unsubscribe, etc.  Applications should {@link #shutdown()} a Siena
+ *  service access point when it is no longer needed.
+ **/
+public interface Siena {
+    /** publish a notification.
+     *
+     *  @param n The notification to publish.
+     *  @see Notification
+     **/
+    public void publish(Notification n) throws SienaException;
+
+    /** subscribes for events matching Filter <b>f</b>.
+     *
+     *  <p>Notice that given the distributed nature of some
+     *  implementations of Siena, there exist race conditions that
+     *  might affect the semantics of subscriptions.  Specifically, a
+     *  subscriber might miss some notifications published before (or
+     *  while) the subscription is processed by Siena.
+     *
+     *  @param n is the subscriber
+     *  @param f is the subscription
+     *  @see #unsubscribe
+     **/
+    public void subscribe(Filter f, Notifiable n) throws SienaException;
+
+    /** subscribes for sequences of events matching pattern <b>p</b>.
+     *
+     *  <p>Notice that given the distributed nature of some
+     *  implementations of Siena interface, there exist race
+     *  conditions that might affect the semantics of subscriptions.
+     *  A subscriber might miss some notifications published before or
+     *  while the subscription is processed by Siena.
+     *
+     *  <p>Also, keep in mind that the current implementation of Siena
+     *  does not enforce any temporal order for the delivery of
+     *  notifications.  This limitation might affect the recognition
+     *  of patterns.  For example, two notifications <em>x</em> and
+     *  <em>y</em>, generated at time <em>t<sub>x</sub></em> and
+     *  <em>t<sub>y</sub></em> respectively, with
+     *  <em>t<sub>x</sub></em> &lt; <em>t<sub>y</sub></em>, in that
+     *  order matching a pattern <em>P=(f<sub>x</sub>
+     *  f<sub>y</sub>)</em>, might in fact reach the subscriber at
+     *  times <em>T<sub>x</sub></em> and <em>T<sub>y</sub></em>, with
+     *  <em>T<sub>x</sub></em> &gt; <em>T<sub>y</sub></em>, in which
+     *  case pattern <em>P</em> would not be matched.
+     *
+     *  @param n is the subscriber
+     *  @param p is the subscription pattern
+     *  @see #unsubscribe
+     **/
+    public void subscribe(Pattern p, Notifiable n) throws SienaException;
+
+    /** cancels the subscriptions, posted by <b>n</b>, whose filter
+     *  <b>f'</b> is covered by filter <b>f</b>.
+     *
+     *  <p>Unsubscriptions might incurr in the same kind of race
+     *  conditions as subscriptions.  Siena will stop sending
+     *  notifications to the subscriber only after it has completed
+     *  the processing of the unsubscriptions.  Due to the distributed
+     *  nature of some implementations of Siena, this might result in
+     *  some additional ``unsolicited'' notifications.
+     *
+     *  @param n is the subscriber
+     *  @see #subscribe
+     **/
+    public void unsubscribe(Filter f, Notifiable n) throws SienaException;
+
+    /** cancels the subscriptions, posted by <b>n</b>, whose pattern
+     *  <b>p'</b> is covered by pattern <b>p</b>.
+     *
+     *  <p>Unsubscriptions might incurr in the same kind of race
+     *  conditions as subscriptions.  Siena will stop sending
+     *  notifications to the subscriber only after it has completed
+     *  the processing of the unsubscription.  Due to the distributed
+     *  nature of some implementations of Siena, this might result in
+     *  some additional ``unsolicited'' notifications.
+     *
+     *  @param n is the subscriber
+     *  @see #subscribe
+     **/
+    public void unsubscribe(Pattern p, Notifiable n) throws SienaException;
+
+    /** cancels <i>all</i> the subscriptions posted by <b>n</b>.
+     *
+     *  @param n is the subscriber
+     *  @see #subscribe
+     **/
+    public void unsubscribe(Notifiable n) throws SienaException;
+
+    /** advertises a set of notifications.
+     *
+     * Tells Siena that the object identified by <b>id</b> might
+     * publish notifications matching the advertisement filter
+     * <b>f</b>.
+     *
+     * @param f advertisement filter.  Notice that this filter is
+     *          interpreted differently than a subscription filter.
+     *          For more information, consult the <a
+     *          href="http://www.cs.colorado.edu/~carzanig/siena/index.html#documents">Siena
+     *          documentation</a>.
+     *
+     * @param id identifier of the publisher
+     * @see #unadvertise
+     **/
+    public void advertise(Filter f, String id) throws SienaException;
+
+    /** cancel previous advertisements.
+     *
+     * Cancels those regarding publisher <b>id</b>, whose
+     * advertisement filter <b>f'</b> is covered by advertisement
+     * filter <b>f</b>.
+     *
+     * @param f advertisement filter.  Notice that this filter is
+     *          interpreted differently than a subscription filter.
+     *          For more information, consult the <a
+     *          href="http://www.cs.colorado.edu/serl/siena/index.html#documents">Siena documentation</a>.
+     *
+     * @param id identifier of the publisher
+     * @see #unadvertise
+     **/
+    public void unadvertise(Filter f, String id) throws SienaException;
+
+    /** cancel <em>all</em> previous advertisements for object <b>id</b>.
+     *
+     * @param id identifier of the publisher
+     * @see #unadvertise
+     **/
+    public void unadvertise(String id) throws SienaException;
+
+
+    /** suspends the delivery of notifications to the given subscriber
+     *  <code>n</code>.
+     *
+     *  @param n subscriber to be suspended
+     *  @see #resume
+     **/
+    public void suspend(Notifiable n) throws SienaException;
+
+    /** resumes the delivery of notifications to the given subscriber
+     *  <code>n</code>.
+     *
+     *  @param n subscriber to be resumed
+     *  @see #resume
+     **/
+    public void resume(Notifiable n) throws SienaException;
+
+    /** closes this Siena service access point.
+     *
+     *  This method releases any system resources associated with the
+     *  access point.  In case this access point is connected to other
+     *  Siena servers, this method will properly disconnect it.
+     **/
+    public void shutdown() throws SienaException;
+}
+
diff --git a/siena/ipaq/SienaException.java b/siena/ipaq/SienaException.java
new file mode 100644
index 0000000..b66ea9b
--- /dev/null
+++ b/siena/ipaq/SienaException.java
@@ -0,0 +1,46 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.lang.String;
+
+/** exception related to Siena
+ */
+public class SienaException extends Exception {
+    //
+    // very simplistic!
+    // ...work in progress...
+    //
+    public SienaException() {
+	super();
+    }
+
+    public SienaException(String s) {
+	super(s);
+    }
+}
diff --git a/siena/ipaq/SienaId.java b/siena/ipaq/SienaId.java
new file mode 100644
index 0000000..ea19c91
--- /dev/null
+++ b/siena/ipaq/SienaId.java
@@ -0,0 +1,48 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This program is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU General Public License
+//  as published by the Free Software Foundation; either version 2
+//  of the License, or (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+class SienaId {
+    static private byte counter = 0;
+    /**
+     * Creates a new unique id.
+     */
+    static public String getId() {
+	Long s = new Long(System.currentTimeMillis());
+	String hn;
+	try {
+	    hn = java.net.InetAddress.getLocalHost().getHostName();
+	} catch (java.net.UnknownHostException ex) {
+	    hn = "-";
+	}
+	return s.toString() +  "." + (new Byte(counter++)).toString()
+	    + "." + hn;
+    }
+}
+
+
diff --git a/siena/ipaq/StartServer.java b/siena/ipaq/StartServer.java
new file mode 100644
index 0000000..e253355
--- /dev/null
+++ b/siena/ipaq/StartServer.java
@@ -0,0 +1,257 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This program is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU General Public License
+//  as published by the Free Software Foundation; either version 2
+//  of the License, or (at your option) any later version.
+//
+//  This program is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//  GNU General Public License for more details.
+//
+//  You should have received a copy of the GNU General Public License
+//  along with this program; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.net.InetAddress;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+
+/**  a utility class that can be used to run a
+ *   <code>HierarchicalDispatcher</code> as a stand-alone Siena
+ *   server.
+ *
+ *   <code>StartServer</code> accepts some command-line parameters to
+ *   set various options of the dispatcher (such as its listener port,
+ *   its identity etc.).  <p>
+ *
+ *   The complete syntax of the command-line options is:
+ *   <p>
+ *
+ *   <code>StartServer</code> [<code>-master</code> <em>handler</em>]
+ *     [<code>-id</code> <em>identity</em>] [<code>-host</code>
+ *     <em>address</em>] [<code>-port</code> <em>port</em>]
+ *     [<code>-monitor</code> <em>hostname</em>] [<code>-err</code>
+ *     <code>off</code> | <code>-</code> | <em>filename</em>]
+ *     [<code>-log</code> <code>off</code> | <code>-</code> |
+ *     <em>filename</em>] [<code>-fail-delay</code> <em>millisec</em>]
+ *     [<code>-fail-count</code> <em>num</em>]
+ *     [<code>-output-threads</code> <em>num</em>]
+ *
+ *   <p>
+ *   <dl>
+ *   <dt><code>-master</code> <em>handler</em><dd> sets the master server for
+ *   this server
+ *
+ *   <dt><code>-id</code> <em>identity</em><dd> explicitly sets the
+ *   identity of this server
+ *
+ *   <dt><code>-host</code> <em>address</em><dd> explicitly sets the
+ *   host address for the receiver of this server.  This option is
+ *   provided in case the JVM can not reliably determine its own host
+ *   address (see {@link TCPPacketReceiver#setHostName(String)} and
+ *   {@link UDPPacketReceiver#setHostName(String)})
+ *
+ *   <dt><code>-udp</code><dd> uses a UDP receiver instead of a TCP
+ *   receiver
+ *
+ *   <dt><code>-ka</code><dd> uses a Keep-Alive receiver instead of a
+ *   plain TCP receiver
+ *
+ *   <dt><code>-port</code> <em>port</em><dd> port number for the packet
+ *   receiver of this Siena server
+ *
+ *   <dt><code>-monitor</code> <em>hostname</em><dd>
+ *
+ *   <dt><code>-err</code> <code>off</code> | <code>-</code> |
+ *   <em>filename</em><dd> redirects the error stream.  <code>-</code>
+ *   means standard output.  <code>off</code> turns off error reporting.
+ *   The default is to send error messages to <code>System.err</code>.
+ *
+ *   <dt><code>-log</code> <code>off</code> | <code>-</code> |
+ *   <em>filename</em><dd> redirects the logging stream.  <code>-</code>
+ *   means standard output.  <code>off</code> turns off error
+ *   reporting. By default logging is turned off.
+ *
+ *   <dt><code>-fail-delay</code> <em>millisec</em><dd> sets {@link
+ *   HierarchicalDispatcher#MaxFailedConnectionsDuration}
+ *
+ *   <dt><code>-fail-count</code> <em>number</em><dd> sets {@link
+ *   HierarchicalDispatcher#MaxFailedConnectionsNumber}
+ *
+ *   <dt><code>-threads</code> <em>number</em><dd> sets {@link
+ *   HierarchicalDispatcher#DefaultThreadCount}
+ *
+ *   </dl>
+ **/
+public class StartServer {
+
+    private static final int R_TCP = 0;
+    private static final int R_UDP = 1;
+    private static final int R_KA = 2;
+
+    static void printUsage() {
+	System.err.println("usage: StartServer [options...]\noptions:\n\t[-master uri]\n\t[-id identity]\n\t[-host address]\n\t[-port port]\n\t[-udp|-ka]\n\t[-monitor hostname]\n\t[-log - | <filename>]\n\t[-err - | off | <filename>]\n\t[-fail-delay <millisec>]\n\t[-fail-count <number>]\n\t[-threads <number>]");
+	System.exit(1);
+    }
+
+    public static void main(String argv[]) {
+	try {
+	    String master = null;
+	    int rtype = R_TCP;
+	    int port = -1;
+            HierarchicalDispatcher siena = null;
+	    String monitor = null;
+	    String identity = null;
+	    String host = null;
+	    PrintStream debugfile = null;
+	    int thread_count = -1;
+	    int max_failout = 2;
+	    long max_timeout = 5000;
+
+	    for (int i = 0; i < argv.length; i++) {
+		if (argv[i].equals("-master")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    master = argv[i];
+		} else if (argv[i].equals("-port")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    try {
+			port = Integer.parseInt(argv[i]);
+		    } catch (NumberFormatException ex) {
+			System.err.println("StartServer: Invalid port number.");
+			printUsage();
+		    }
+		} else if (argv[i].equals("-fail-count")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    try {
+			max_failout = Integer.parseInt(argv[i]);
+		    } catch (NumberFormatException ex) {
+			System.err.println("StartServer: Invalid fail count.");
+			printUsage();
+		    }
+		} else if (argv[i].equals("-threads")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    try {
+			thread_count = Integer.parseInt(argv[i]);
+		    } catch (NumberFormatException ex) {
+			System.err.println("StartServer: Invalid thread count.");
+			printUsage();
+		    }
+		} else if (argv[i].equals("-fail-delay")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    try {
+			max_timeout = Long.parseLong(argv[i]);
+		    } catch (NumberFormatException ex) {
+			System.err.println("StartServer: Invalid fail delay.");
+			printUsage();
+		    }
+		} else if (argv[i].equals("-monitor")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    monitor = argv[i];
+		    Monitor.setAddress(InetAddress.getByName(monitor));
+		} else if (argv[i].equals("-udp")) {
+		    rtype = R_UDP;
+		} else if (argv[i].equals("-ka")) {
+		    rtype = R_KA;
+		} else if (argv[i].equals("-id")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    identity = argv[i];
+		} else if (argv[i].equals("-host")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    host = argv[i];
+		} else if (argv[i].equals("-err")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    if(argv[i].equals("-")) {
+			Logging.setErrorStream(System.out);
+		    } else if(argv[i].equals("off")) {
+			Logging.setErrorStream(null);
+		    } else {
+			Logging.setErrorStream(new PrintStream(new FileOutputStream(argv[i])));
+		    }
+		} else if (argv[i].equals("-log")) {
+		    if (++i >= argv.length)
+			printUsage();
+		    if(argv[i].equals("-")) {
+			Logging.setLogStream(System.out);
+		    } else if(argv[i].equals("off")) {
+			Logging.setLogStream(null);
+		    } else {
+			Logging.setLogStream(new PrintStream(new FileOutputStream(argv[i])));
+		    }
+		} else {
+		    printUsage();
+		}
+	    }
+
+	    if (identity == null) {
+		siena = new HierarchicalDispatcher();
+	    } else {
+		siena = new HierarchicalDispatcher(identity);
+	    }
+	    siena.MaxFailedConnectionsNumber = max_failout;
+	    siena.MaxFailedConnectionsDuration = max_timeout;
+
+	    port = (port == -1) ? SENP.DEFAULT_PORT : port;
+	    PacketReceiver receiver;
+	    switch (rtype) {
+	    case R_TCP:  {
+		TCPPacketReceiver r;
+		r = new TCPPacketReceiver(port);
+		if (host != null) r.setHostName(host);
+		receiver = r;
+		break;
+	    }
+	    case R_UDP: {
+		UDPPacketReceiver r;
+		r = new UDPPacketReceiver(port);
+		if (host != null) r.setHostName(host);
+		receiver = r;
+		break;
+	    }
+	    case R_KA: {
+		KAPacketReceiver r;
+		r = new KAPacketReceiver(port);
+		if (host != null) r.setHostName(host);
+		receiver = r;
+		break;
+	    }
+	    default:
+		receiver = null;
+	    }
+	    if (thread_count < 0) {
+		siena.setReceiver(receiver);
+	    } else {
+		siena.setReceiver(receiver, thread_count);
+	    }
+	    if (master != null) siena.setMaster(master);
+	    if (thread_count == 0)
+		siena.run();
+	}
+	catch (Exception e) {
+	    System.err.println(e.toString());
+	}
+    }
+}
diff --git a/siena/ipaq/TCPPacketReceiver.java b/siena/ipaq/TCPPacketReceiver.java
new file mode 100644
index 0000000..595ce4c
--- /dev/null
+++ b/siena/ipaq/TCPPacketReceiver.java
@@ -0,0 +1,175 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+// $Id$
+//
+
+package siena;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/** receives packets through a TCP port.
+ *
+ *  Receives packets through one-time connections to a TCP port.
+ *  Accepts connections to a local port, reads one packet from the
+ *  accepted socket, and closes the socket.
+ **/
+public class TCPPacketReceiver implements PacketReceiver {
+    static final String	protocol_name = "senp";
+    private ServerSocket	port;
+    private byte[]		my_uri;
+
+    /** create a receiver listening to the given port.
+     *
+     *  @param port_number must be a valid TCP port number, or it can
+     *         be 0 in which case a random port is used
+     *
+     *  @exception if an I/O error occurs when opening the socket.
+     *             typically, when the given port is already in use.
+     **/
+    public TCPPacketReceiver(int port_number) throws IOException {
+	port = new ServerSocket(port_number);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** create a receiver listening to the given port with a given
+     *  maximum queue for TCP connections.
+     *
+     *  @param port_number must be a valid TCP port number, or it can
+     *         be 0 in which case a random port is used
+     *
+     *  @exception if an I/O error occurs when opening the socket.
+     *             typically, when the given port is already in use.
+     **/
+    public TCPPacketReceiver(int port_number, int qsize) throws IOException {
+	port = new ServerSocket(port_number, qsize);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    public TCPPacketReceiver(ServerSocket s) throws UnknownHostException {
+	port = s;
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** explicitly set the address of this packet receiver.
+     *
+     *  This method allows to set the host name or IP address
+     *  explicitly.  This might be necessary in the cases in which the
+     *  java VM can not figure that out reliably.
+     **/
+    synchronized public void setHostName(String hostname) {
+	my_uri = (protocol_name + "://" + hostname + ":"
+		  + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** uri of this packet receiver.
+     *
+     *  uses the following schema syntax:<br>
+     *
+     *  <code>senp://</code><em>host</em><em>[<code>:</code>port]</em>
+     **/
+    public byte[] uri() {
+	return my_uri;
+    }
+
+    synchronized public void shutdown() {
+	if (port == null) return;
+	try {
+	    port.close();
+	    //
+	    // this should terminate all the connection handlers
+	    // attached to that port.  They should receive an
+	    // IOException on accept() ...they should, but on some
+	    // implementations of the JVM they don't.  In
+	    // particular, jvm-1.3rc1-linux-i386 is buggy.
+	    //
+	} catch (IOException ex) {
+	    Logging.exerr(ex);
+	    //
+	    // what can I do here? ...work in progress...
+	    //
+	}
+	port = null;
+    }
+
+    public int receive(byte[] buf) throws PacketReceiverException {
+	try {
+	    Socket sock;
+	    //
+	    // I use this variable to avoid a race condition that may
+	    // occurr if someone calls shutdown() after port == null,
+	    // and before port.accept().
+	    //
+	    ServerSocket p = port;
+	    if (p == null) throw(new PacketReceiverClosed());
+	    try {
+		sock = p.accept();
+	    } catch (IOException ex) {
+		//
+		// I interpret this as a shutdown().
+		//
+		throw new PacketReceiverClosed(ex);
+	    }
+
+	    java.io.InputStream input = sock.getInputStream();
+
+	    int offset = 0;
+	    int res;
+
+	    try {
+		while((res = input.read(buf, offset, buf.length - offset)) >= 0)
+		    offset += res;
+		sock.close();
+	    } catch (Exception ex) {
+		Logging.exerr(ex);
+		throw(new PacketReceiverException(ex.toString()));
+	    }
+	    return offset;
+	} catch (java.io.IOException ex) {
+	    //
+	    // port closed.
+	    //
+	    return 0;
+	}
+    }
+
+    /** <em>not yet implemented</em>.
+     **/
+    public int receive(byte[] buf, long timeout) {
+	//
+	// not yet implemented
+	//
+	return -1;
+    }
+}
diff --git a/siena/ipaq/TCPPacketSender.java b/siena/ipaq/TCPPacketSender.java
new file mode 100644
index 0000000..b651edf
--- /dev/null
+++ b/siena/ipaq/TCPPacketSender.java
@@ -0,0 +1,109 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.io.IOException;
+
+/** packet sender based on one-time TCP connections
+ **/
+class TCPPacketSender implements PacketSender {
+    private InetAddress		ip_address;
+    private int			port;
+
+    public TCPPacketSender(String h)
+	throws InvalidSenderException, java.net.UnknownHostException {
+
+	if (h.indexOf("//", 0) != 0)
+	    throw (new InvalidSenderException("expecting `//'"));
+
+	int port_end_pos = -1;
+	int host_end_pos = h.indexOf(":", 2);
+
+	if (host_end_pos < 0) {
+	    port = -1;
+	    host_end_pos = h.indexOf("/", 2);
+	    if (host_end_pos < 0) host_end_pos = h.length();
+	} else {
+	    port_end_pos = h.indexOf("/", host_end_pos);
+	    if (port_end_pos < 0) port_end_pos = h.length();
+
+	    if (host_end_pos+1 < port_end_pos) {
+		port = Integer.decode(h.substring(host_end_pos+1,
+						  port_end_pos)).intValue();
+	    } else {
+		port = -1;
+	    }
+	}
+	String hostname = h.substring(2, host_end_pos);
+	if (port == -1) {
+	    port = SENP.SERVER_PORT;
+	}
+	ip_address = InetAddress.getByName(hostname);
+    }
+
+    public void send(byte[] packet) throws PacketSenderException {
+	try {
+	    Socket s = new Socket(ip_address, port);
+	    //s.setSendBufferSize(65535);
+	    s.getOutputStream().write(packet);
+	    s.close();
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    public void send(byte[] packet, int offset, int len)
+	throws PacketSenderException {
+	try {
+	    Socket s = new Socket(ip_address, port);
+	    //s.setSendBufferSize(65535);
+	    s.getOutputStream().write(packet, offset, len);
+	    s.close();
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    public void send(byte[] packet, int len)
+	throws PacketSenderException {
+	try {
+	    Socket s = new Socket(ip_address, port);
+	    //s.setSendBufferSize(65535);
+	    s.getOutputStream().write(packet, 0, len);
+	    s.close();
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    public String toString() {
+	return "senp://" + ip_address + ":" + port;
+    }
+}
diff --git a/siena/ipaq/ThinClient.java b/siena/ipaq/ThinClient.java
new file mode 100644
index 0000000..9bc1c09
--- /dev/null
+++ b/siena/ipaq/ThinClient.java
@@ -0,0 +1,509 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2001 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.ServerSocket;
+import java.io.IOException;
+import com.sun.java.util.collections.Map;
+import com.sun.java.util.collections.HashMap;
+import com.sun.java.util.collections.Iterator;
+
+/** <em>thin</em> interface to the Siena event notification service.
+ *
+ *  <code>ThinClient</code> does not provide an event notification
+ *  service by itself, but rather functions as a connection to an
+ *  external Siena server.  Therefore, a <code>ThinClient</code>
+ *  object must be configured (constructed) with the handler of its
+ *  external Siena server.  <p>
+ *
+ *  In the simplest case, a <code>ThinClient</code> can be used only to
+ *  publish notifications.  For example:
+ *
+ *  <code><pre>
+ *      ThinClient siena;
+ *      siena = new ThinClient("senp://siena.dot.org:2345");
+ *      Notification n = new Notification();
+ *      n.putAttribute("name", "Antonio");
+ *      siena.publish(n);
+ *  </pre></code>
+ *
+ *  <code>ThinClient</code> implements the {@link Siena} interface, so
+ *  it can also be used to subscribe and unsubscribe.
+ *  <code>ThinClient</code> uses a {@link PacketReceiver} to receive
+ *  notifications from the server.
+ *
+ *  @see HierarchicalDispatcher
+ *  @see Siena
+ *  @see PacketReceiver
+ **/
+public class ThinClient implements Siena, Runnable {
+    private byte[]		sndbuf		= new byte[SENP.MaxPacketLen];
+    private byte[]		master_id	= null;
+    private byte[]		master_handler	= null;
+    private PacketSender	master		= null;
+    private PacketReceiver	listener	= null;
+    private String		my_id		= null;
+    private Map 		subscribers	= null;
+
+    private SENPPacket		pkt = new SENPPacket();
+
+    static PacketSenderFactory	default_sender_factory
+						= new GenericSenderFactory();
+
+    /** default packet-sender factory for ThinClient interfaces
+     *
+     *  every new ThinClient uses this factory to create its
+     *  connection to its master server
+     **/
+    static public void setDefaultPacketSenderFactory(PacketSenderFactory f) {
+	default_sender_factory = f;
+    }
+
+    /** number of threads handling external packets.
+     *
+     *	The default value of <code>ReceiverThreads</code> is 4.  This
+     *	value is used as the default number of receiver threads.
+     *
+     *	@see #setReceiver(PacketReceiver)
+     **/
+    public  int			ReceiverThreads			= 4;
+
+    /** creates a thin client connected to a Siena server.
+
+	@param server the uri of the server to connect to
+                      (e.g., "senp://host.domain.net:7654")
+    **/
+    public ThinClient(String server) throws InvalidSenderException {
+	master = default_sender_factory.createPacketSender(server);
+	my_id = SienaId.getId();
+	subscribers = new HashMap();
+	Monitor.add_node(my_id.getBytes(), Monitor.ThinClientNode);
+    }
+
+    private synchronized byte[] mapSubscriber(Notifiable n) {
+	if (n == null) return null;
+	String newid = my_id + Integer.toString(n.hashCode());
+	if (subscribers.put(newid, n) == null) {
+	    //
+	    // first time we see this subscriber, so we notify the monitor
+	    //
+	    Monitor.add_node(newid.getBytes(), Monitor.ObjectNode);
+	    Monitor.connect(newid.getBytes(), my_id.getBytes());
+	}
+	return newid.getBytes();
+    }
+
+    private synchronized byte[] haveSubscriber(Notifiable n) {
+	if (n == null) return null;
+	String newid = my_id + Integer.toString(n.hashCode());
+	if (subscribers.containsKey(newid)) {
+	    return newid.getBytes();
+	} else {
+	    return null;
+	}
+    }
+
+    private synchronized void removeSubscriber(Notifiable n) {
+	if (n != null) {
+	    String sid = my_id + Integer.toString(n.hashCode());
+	    subscribers.remove(sid);
+	    Monitor.remove_node(sid.getBytes());
+	}
+    }
+
+    private synchronized Notifiable mapSubscriber(byte[] id) {
+	if (id == null) return null;
+	return (Notifiable)subscribers.get(new String(id));
+    }
+
+    /** sets the <em>packet receiver</em> for this server.
+     *
+     *	A <em>packet receiver</em> accepts notifications,
+     *	subscriptions, and other requests on some communication
+     *	channel.  <code>setReceiver</code> will shut down any
+     *	previously activated receiver for this dispatcher.  This
+     *	method does not guarantee a transactional switch to a new
+     *	receiver.  This means that some requests might get lost while
+     *	the server has closed the old port and before it reopens the
+     *	new port.
+     *
+     *  <p>This method simply calls {@link #setReceiver(PacketReceiver,
+     *  int)} using {@link #ReceiverThreads} as a default value.
+     *
+     *	@param r is the receiver
+     *
+     *  @see #shutdown()
+     *  @see #setReceiver(PacketReceiver, int)
+     **/
+    public void setReceiver(PacketReceiver r) {
+	setReceiver(r, ReceiverThreads);
+    }
+
+    /** sets the <em>packet receiver</em> for this server.
+     *
+     *	A <em>packet receiver</em> accepts notifications,
+     *	subscriptions, and other requests on some communication
+     *	channel.  <code>setReceiver</code> will shut down any
+     *	previously activated receiver for this dispatcher.  This
+     *	method does not guarantee a transactional switch to a new
+     *	receiver.  This means that some requests might get lost while
+     *	the server has closed the old port and before it reopens the
+     *	new port.
+     *
+     *	@param r is the receiver
+     *  @param threads is the number of threads associated with the
+     *         receiver, and therefore to the whole server.
+     *
+     *  @see #shutdown()
+     **/
+    synchronized public void setReceiver(PacketReceiver r, int threads) {
+	if (listener != null) {
+	    try {
+		listener.shutdown();
+	    } catch (PacketReceiverException ex) {
+		Logging.exerr(ex);
+	    }
+	    //
+	    // this should send a PacketReceiverClosed exception to
+	    // every thread that is waiting for packets on the old
+	    // listener, which will make them exit normally.  However,
+	    // because of bugs in the JVM, or because of bad
+	    // implementations of packetReceiver, this might not be
+	    // true.  ...work in progress...
+	    //
+	}
+	listener = r;
+
+	if (master != null && !subscribers.isEmpty()) {
+	    pkt.init();
+	    pkt.method = SENP.MAP;
+	    pkt.id = my_id.getBytes();
+	    pkt.handler = listener.uri();
+	    try {
+		master.send(pkt.buf, pkt.encode());
+	    } catch (Exception ex) {
+		Logging.exerr(ex);
+		//
+		// I should really do something here
+		// ...work in progress...
+		//
+	    }
+	}
+	//
+	// now fires off the threads that listen to this port
+	//
+	while (threads-- > 0)
+	    (new Thread(this)).start();
+    }
+
+    public void run() {
+	SENPPacket rpkt = new SENPPacket();
+	int res;
+	while(true) {
+	    try {
+		PacketReceiver r = listener;
+		if (r == null) return;
+		res = r.receive(rpkt.buf);
+		rpkt.init(res);
+		rpkt.decode();
+		if (rpkt.ttl > 0) {
+		    if (rpkt.method ==  SENP.PUB) {
+			Notifiable n = mapSubscriber(rpkt.to);
+			if (n != null) {
+			    Monitor.notify(rpkt.id, my_id.getBytes());
+			    Monitor.notify(my_id.getBytes(), rpkt.to);
+			    if (rpkt.event != null) {
+				n.notify(rpkt.event);
+			    } else if (rpkt.events != null) {
+				n.notify(rpkt.events);
+			    }
+			} else {
+			    Logging.prlnerr("ThinClient: warning: unknown id: " + new String(rpkt.id));
+			}
+		    } else {
+			Logging.prlnerr("ThinClient: warning: unable to handle method: " + rpkt.method);
+		    }
+		}
+	    } catch (PacketReceiverClosed ex) {
+		if (ex.getIOException() != null)
+		    Logging.exerr(ex);
+		return;
+	    } catch (PacketReceiverFatalError ex) {
+		Logging.exerr(ex);
+		return;
+	    } catch (PacketReceiverException ex) {
+		//
+		// non fatal error: just log it and loop
+		//
+		Logging.exerr(ex);
+	    } catch (Exception ex) {
+		Logging.exerr(ex);
+	    }
+	}
+    }
+
+    /** suspends the delivery of notifications for a subscriber.
+     *
+     *	This causes the Siena server to stop sending notification to
+     *	the given subscriber.  The server correctly maintains all the
+     *	existing subscriptions so that the flow of notification can be
+     *	later resumed (with {@link #resume(Notifiable)}).
+     *
+     *	@see #resume(Notifiable)
+     **/
+    synchronized public void suspend(Notifiable n) throws SienaException {
+	pkt.id = haveSubscriber(n);
+	if (pkt.id == null) return;
+	pkt.method = SENP.SUS;
+	pkt.to = master_handler;
+	pkt.id = mapSubscriber(n);
+	pkt.handler = listener.uri();
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    /** resumes the delivery of notifications for a subscriber.
+     *
+     *	This causes the Siena (master) server to resume sending
+     *	notification to the given subscriber.
+     *
+     *	@see #suspend(Notifiable)
+     **/
+    synchronized public void resume(Notifiable n) throws SienaException {
+	pkt.id = haveSubscriber(n);
+	if (pkt.id == null) return;
+	pkt.method = SENP.RES;
+	pkt.to = master_handler;
+	pkt.handler = listener.uri();
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    /** returns the <em>handler</em> of the Siena server associated
+	with this dispatcher.
+
+	@return URI of the master server.
+
+	@see #ThinClient(String)
+    **/
+    synchronized public String getServer() {
+	if (master_handler == null) return null;
+	return new String(master_handler);
+    }
+
+    synchronized private void unsubscribeAll() {
+	if (master != null) {
+	    pkt.init();
+	    pkt.method = SENP.BYE;
+	    pkt.to = master_handler;
+	    Iterator i = subscribers.keySet().iterator();
+	    while(i.hasNext()) {
+		try {
+		    pkt.id = ((String)i.next()).getBytes();
+		    master.send(pkt.buf, pkt.encode());
+		} catch (Exception ex) {
+		    Logging.exerr(ex);
+		    //
+		    // what should I do here?
+		    // ...work in progress...
+		    //
+		}
+	    }
+	    subscribers.clear();
+	    master = null;
+	    master_handler = null;
+	}
+    }
+
+    synchronized public void publish(Notification n) throws SienaException {
+	if (n == null) return;
+
+	pkt.init();
+	pkt.event = n;
+	pkt.method = SENP.PUB;
+	pkt.id = my_id.getBytes();
+	pkt.to = master_handler;
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    public void subscribe(Filter f, Notifiable n) throws SienaException {
+	if (n == null) return;
+	if (f == null) {
+	    //
+	    // null filters are not allowed in subscriptions this is a
+	    // design choice, we could accept null filters with the
+	    // semantics of the universal filter: one that matches
+	    // every notification
+	    //
+	    throw (new SienaException("null filter"));
+	}
+	if (listener == null) {
+	    try {
+		setReceiver(new TCPPacketReceiver(0));
+	    } catch (IOException ex) {
+		throw (new SienaException(ex.toString()));
+	    }
+	}
+	pkt.init();
+	pkt.filter = f;
+	pkt.method = SENP.SUB;
+	pkt.id = mapSubscriber(n);
+	pkt.handler = listener.uri();
+	pkt.to = master_handler;
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    public void subscribe(Pattern p, Notifiable n) throws SienaException {
+	if (n == null) return;
+	if (p == null) {
+	    //
+	    // null patterns are not allowed in subscriptions
+	    //
+	    throw (new SienaException("null pattern"));
+	}
+	if (listener == null) {
+	    try {
+		setReceiver(new TCPPacketReceiver(0));
+	    } catch (IOException ex) {
+		throw (new SienaException(ex.toString()));
+	    }
+	}
+	pkt.init();
+	pkt.pattern = p;
+	pkt.method = SENP.SUB;
+	pkt.id = mapSubscriber(n);
+	pkt.handler = listener.uri();
+	pkt.to = master_handler;
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    public void unsubscribe(Filter f, Notifiable n) throws SienaException {
+	if (n == null || listener == null) return;
+
+	pkt.init();
+	pkt.id = haveSubscriber(n);
+	if (pkt.id != null) {
+	    pkt.handler = listener.uri();
+	    pkt.to = master_handler;
+	    if (f == null) {
+		removeSubscriber(n);
+		pkt.method = SENP.BYE;
+	    } else {
+		pkt.method = SENP.UNS;
+		pkt.filter = f;
+	    }
+	    master.send(pkt.buf, pkt.encode());
+	}
+    }
+
+    public void unsubscribe(Pattern p, Notifiable n) throws SienaException {
+	if (n == null || listener == null) return;
+
+	pkt.init();
+	pkt.id = haveSubscriber(n);
+	if (pkt.id != null) {
+	    pkt.handler = listener.uri();
+	    pkt.to = master_handler;
+	    if (p == null) {
+		removeSubscriber(n);
+		pkt.method = SENP.BYE;
+	    } else {
+		pkt.method = SENP.UNS;
+		pkt.pattern = p;
+	    }
+	    master.send(pkt.buf, pkt.encode());
+	}
+    }
+
+    /** closes this dispatcher.
+
+        If this dispatcher has an active listener then closes the
+        active listener.  It also unsubscribes everything with its
+        master server.
+
+        @see #setReceiver(PacketReceiver)
+     **/
+    synchronized public void shutdown() {
+	unsubscribeAll();
+	if (listener != null)
+	    try {
+		listener.shutdown();
+	    } catch (PacketReceiverException ex) {
+		Logging.exerr(ex);
+	    }
+	listener = null;
+    }
+
+    synchronized public void advertise(Filter f, String id)
+	throws SienaException {
+	if (id == null) return;
+	//
+	// I haven't thought about what to do here.
+	//
+	if (f == null) {
+	    //
+	    // I haven't thought about what to do here.
+	    //
+	    throw (new SienaException("null filter"));
+	}
+	pkt.init();
+	pkt.filter = f;
+	pkt.method = SENP.ADV;
+	pkt.id = id.getBytes();
+	pkt.handler = listener.uri();
+	pkt.to = master_handler;
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    synchronized public void unadvertise(Filter f, String id)
+	throws SienaException {
+	if (id == null) return;
+	//
+	// I haven't thought about what to do here.
+	//
+	pkt.init();
+	pkt.id = id.getBytes();
+	pkt.handler = listener.uri();
+	pkt.to = master_handler;
+	pkt.method = SENP.UNA;
+	pkt.filter = f;
+	//
+	// should I have a special method for UNA all (when f == null)?
+	// ...work in progress...
+	//
+	master.send(pkt.buf, pkt.encode());
+    }
+
+    public void unadvertise(String id) throws SienaException {
+	unadvertise(null, id);
+    }
+
+    public void unsubscribe(Notifiable n) throws SienaException {
+	unsubscribe((Filter)null, n);
+    }
+}
diff --git a/siena/ipaq/TimeoutExpired.java b/siena/ipaq/TimeoutExpired.java
new file mode 100644
index 0000000..54cb4e1
--- /dev/null
+++ b/siena/ipaq/TimeoutExpired.java
@@ -0,0 +1,35 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+/** a timeout has expired during Siena operations */
+public class TimeoutExpired extends SienaException {
+    public TimeoutExpired(java.lang.String s) {
+	super(s);
+    }
+}
diff --git a/siena/ipaq/UDPPacketReceiver.java b/siena/ipaq/UDPPacketReceiver.java
new file mode 100644
index 0000000..d236729
--- /dev/null
+++ b/siena/ipaq/UDPPacketReceiver.java
@@ -0,0 +1,130 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+// $Id$
+//
+package siena;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.net.InetAddress;
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+import java.net.Socket;
+
+/** receives packets through a UDP port.
+ **/
+public class UDPPacketReceiver implements PacketReceiver {
+    static final String		protocol_name = "udp+senp";
+    private DatagramSocket	port;
+    private DatagramPacket	packet;
+    private byte[]		my_uri;
+
+    /** create a receiver listening to the given UDP port.
+     *
+     *  @param port_number must be a valid UDP port number, or it can
+     *         be 0 in which case a random port is used
+     **/
+    public UDPPacketReceiver(int port_number) throws IOException {
+	port = new DatagramSocket(port_number);
+	//	port.setReceiveBufferSize(SENP.MaxPacketLen);
+	packet = new DatagramPacket(new byte[1], 1);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    public UDPPacketReceiver(DatagramSocket s) throws UnknownHostException {
+	port = s;
+	packet = new DatagramPacket(new byte[1], 1);
+	my_uri = (protocol_name + "://"
+		  + InetAddress.getLocalHost().getHostAddress()
+		  + ":" + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** explicitly set the address of this packet receiver.
+     *
+     *  This method allows to set the host name or IP address
+     *  explicitly.  This might be necessary in the cases in which the
+     *  java VM can not figure that out reliably.
+     *
+     **/
+    synchronized public void setHostName(String hostname) {
+	my_uri = (protocol_name + "://" + hostname + ":"
+		  + Integer.toString(port.getLocalPort())).getBytes();
+    }
+
+    /** uri of this packet receiver.
+     *
+     *  uses the following schema syntax:<br>
+     *
+     *  <code>udp+senp://</code><em>host</em><em>[<code>:</code>port]</em>
+     **/
+    public byte[] uri() {
+	return my_uri;
+    }
+
+    synchronized public void shutdown() {
+	System.out.println("shutdown() called...");
+	if (port == null) return;
+	System.out.println("closing port...");
+	port.close();
+	port = null;
+    }
+
+    synchronized private DatagramSocket getPort() {
+	return port;
+    }
+
+    public int receive(byte[] buf) throws PacketReceiverException {
+
+	if (port == null) throw(new PacketReceiverClosed());
+	try {
+	    synchronized (packet) {
+		packet.setData(buf);
+		System.out.println("receiving...");
+		port.receive(packet);
+		System.out.println("read packet: " + packet.getLength());
+		return packet.getLength();
+	    }
+	} catch (Exception ex) {
+	    if (port != null) {
+		Logging.prlnerr("can't get packet on UDP port: "
+				+ Integer.toString(port.getLocalPort()));
+		Logging.exerr(ex);
+		throw(new PacketReceiverException(ex.toString()));
+	    }
+	}
+	throw new PacketReceiverClosed();
+    }
+
+    /** <em>not yet implemented</em>.
+     **/
+    public int receive(byte[] buf, long timeout) {
+	//
+	// not yet implemented
+	//
+	return -1;
+    }
+}
diff --git a/siena/ipaq/UDPPacketSender.java b/siena/ipaq/UDPPacketSender.java
new file mode 100644
index 0000000..6d18521
--- /dev/null
+++ b/siena/ipaq/UDPPacketSender.java
@@ -0,0 +1,131 @@
+//
+//  This file is part of Siena, a wide-area event notification system.
+//  See http://www.cs.colorado.edu/serl/siena/
+//
+//  Author: Antonio Carzaniga <carzanig@cs.colorado.edu>
+//  See the file AUTHORS for full details.
+//
+//  Copyright (C) 1998-2002 University of Colorado
+//
+//  This library is free software; you can redistribute it and/or
+//  modify it under the terms of the GNU Lesser General Public
+//  License as published by the Free Software Foundation; either
+//  version 2.1 of the License, or (at your option) any later version.
+//
+//  This library is distributed in the hope that it will be useful,
+//  but WITHOUT ANY WARRANTY; without even the implied warranty of
+//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+//  Lesser General Public License for more details.
+//
+//  You should have received a copy of the GNU Lesser General Public
+//  License along with this library; if not, write to the Free Software
+//  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307,
+//  USA, or send email to serl@cs.colorado.edu.
+//
+//
+// $Id$
+//
+package siena;
+
+import java.net.InetAddress;
+import java.net.DatagramSocket;
+import java.net.DatagramPacket;
+import java.io.IOException;
+
+/** UDP packet sender
+ **/
+class UDPPacketSender implements PacketSender {
+    private InetAddress		ip_address;
+    private int			port;
+    private DatagramSocket	socket;
+    private DatagramPacket	packet;
+
+    public UDPPacketSender(String h)
+	throws InvalidSenderException,
+	       java.net.UnknownHostException,
+	       java.net.SocketException {
+
+	if (h.indexOf("//", 0) != 0)
+	    throw (new InvalidSenderException("expecting `//'"));
+
+	int port_end_pos = -1;
+	int host_end_pos = h.indexOf(":", 2);
+
+	if (host_end_pos < 0) {
+	    port = -1;
+	    host_end_pos = h.indexOf("/", 2);
+	    if (host_end_pos < 0) host_end_pos = h.length();
+	} else {
+	    port_end_pos = h.indexOf("/", host_end_pos);
+	    if (port_end_pos < 0) port_end_pos = h.length();
+
+	    if (host_end_pos+1 < port_end_pos) {
+		port = Integer.decode(h.substring(host_end_pos+1,
+						  port_end_pos)).intValue();
+	    } else {
+		port = -1;
+	    }
+	}
+	String hostname = h.substring(2, host_end_pos);
+	if (port == -1) {
+	    port = SENP.SERVER_PORT;
+	}
+	ip_address = InetAddress.getByName(hostname);
+	socket = new DatagramSocket();
+	//socket.setSendBufferSize(SENP.MaxPacketLen);
+	//
+	// looks like I have to use a buffer in the constructor even
+	// though I will never use that buffer to send data
+	//
+	packet = new DatagramPacket(new byte[1], 1, ip_address, port);
+    }
+
+    synchronized public void shutdown() {
+	if (socket == null) return;
+	socket.close();
+	socket = null;
+    }
+
+    synchronized public void send(byte[] buf) throws PacketSenderException {
+	if (socket == null)
+	    throw new PacketSenderException("sender shut down");
+	try {
+	    packet.setLength(buf.length);
+	    packet.setData(buf);
+	    socket.send(packet);
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    synchronized public void send(byte[] buf, int offset, int len)
+	throws PacketSenderException {
+	if (socket == null)
+	    throw new PacketSenderException("sender shut down");
+	try {
+	    packet.setLength(buf.length);
+	    packet.setData(buf);
+	    socket.send(packet);
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    synchronized public void send(byte[] buf, int len)
+	throws PacketSenderException {
+	if (socket == null)
+	    throw new PacketSenderException("sender shut down");
+	try {
+	    packet.setLength(len);
+	    packet.setData(buf);
+	    packet.setLength(len);
+	    socket.send(packet);
+	} catch (IOException ex) {
+	    throw new PacketSenderException(ex.getMessage());
+	}
+    }
+
+    public String toString() {
+	return "udp+senp://" + ip_address + ":" + port;
+    }
+}
diff --git a/siena/ipaq/error.txt b/siena/ipaq/error.txt
new file mode 100644
index 0000000..e69de29
diff --git a/siena/ipaq/grep b/siena/ipaq/grep
new file mode 100644
index 0000000..620929b
--- /dev/null
+++ b/siena/ipaq/grep
@@ -0,0 +1,37 @@
+AttributeConstraint.java
+AttributeValue.java
+Covering.java
+DirectSENPInterface.java
+Filter.java
+GenericSenderFactory.java
+HierarchicalDispatcher.java
+InvalidSenderException.java
+KAPacketReceiver.java
+KAPacketSender.java
+Logging.java
+Monitor.java
+Notifiable.java
+NotificationBuffer.java
+Notification.java
+Op.java
+PacketReceiverClosed.java
+PacketReceiverException.java
+PacketReceiverFatalError.java
+PacketReceiver.java
+PacketSenderException.java
+PacketSenderFactory.java
+PacketSender.java
+Pattern.java
+SENPInvalidFormat.java
+SENP.java
+SENPPacket.java
+SienaException.java
+SienaId.java
+Siena.java
+StartServer.java
+TCPPacketReceiver.java
+TCPPacketSender.java
+ThinClient.java
+TimeoutExpired.java
+UDPPacketReceiver.java
+UDPPacketSender.java