Initial revision
diff --git a/Demo/rpc/T.py b/Demo/rpc/T.py
new file mode 100644
index 0000000..24d5e8a
--- /dev/null
+++ b/Demo/rpc/T.py
@@ -0,0 +1,19 @@
+import sys, os, time
+
+def TSTART():
+	global t0, t1
+	u, s, cu, cs = os.times()
+	t0 = u+cu, s+cs, time.millitimer()
+
+def TSTOP(*label):
+	global t0, t1
+	u, s, cu, cs = os.times()
+	t1 = u+cu, s+cs, time.millitimer()
+	tt = []
+	for i in range(3):
+		tt.append(t1[i] - t0[i])
+	[u, s, r] = tt
+	msg = ''
+	for x in label: msg = msg + (x + ' ')
+	msg = msg + `u` + ' user, ' + `s` + ' sys, ' + `r*0.001` + ' real\n'
+	sys.stderr.write(msg)
diff --git a/Demo/rpc/mountclient.py b/Demo/rpc/mountclient.py
new file mode 100644
index 0000000..021de8c
--- /dev/null
+++ b/Demo/rpc/mountclient.py
@@ -0,0 +1,166 @@
+# Mount RPC client -- RFC 1094 (NFS), Appendix A
+
+# This module demonstrates how to write your own RPC client in Python.
+# Since there is no RPC compiler for Python (yet), you must first
+# create classes derived from Packer and Unpacker to handle the data
+# types for the server you want to interface to.  You then write the
+# client class.  If you want to support both the TCP and the UDP
+# version of a protocol, use multiple inheritance as shown below.
+
+
+from rpc import Packer, Unpacker, TCPClient, UDPClient
+
+MOUNTPROG = 100005
+MOUNTVERS = 1
+
+FHSIZE = 32
+
+
+# Packer derived class for Mount protocol clients.
+# The only thing we need to pack beyond basic types is an 'fhandle'
+
+class MountPacker(Packer):
+
+	def pack_fhandle(self, fhandle):
+		self.pack_fopaque(FHSIZE, fhandle)
+
+
+# Unpacker derived class for Mount protocol clients.
+# The important types we need to unpack are fhandle, fhstatus,
+# mountlist and exportlist; mountstruct, exportstruct and groups are
+# used to unpack components of mountlist and exportlist and the
+# corresponding functions are passed as function argument to the
+# generic unpack_list function.
+
+class MountUnpacker(Unpacker):
+
+	def unpack_fhandle(self):
+		return self.unpack_fopaque(FHSIZE)
+
+	def unpack_fhstatus(self):
+		status = self.unpack_uint()
+		if status == 0:
+			fh = self.unpack_fhandle()
+		else:
+			fh = None
+		return status, fh
+
+	def unpack_mountlist(self):
+		return self.unpack_list(self.unpack_mountstruct)
+
+	def unpack_mountstruct(self):
+		hostname = self.unpack_string()
+		directory = self.unpack_string()
+		return (hostname, directory)
+
+	def unpack_exportlist(self):
+		return self.unpack_list(self.unpack_exportstruct)
+
+	def unpack_exportstruct(self):
+		filesys = self.unpack_string()
+		groups = self.unpack_groups()
+		return (filesys, groups)
+
+	def unpack_groups(self):
+		return self.unpack_list(self.unpack_string)
+
+
+# These are the procedures specific to the Mount client class.
+# Think of this as a derived class of either TCPClient or UDPClient.
+
+class PartialMountClient:
+
+	# This method is called by Client.init to initialize
+	# self.packer and self.unpacker
+	def addpackers(self):
+		self.packer = MountPacker().init()
+		self.unpacker = MountUnpacker().init('')
+
+	# The methods Mnt, Dump etc. each implement one Remote
+	# Procedure Call.  Their general structure is
+	#  self.start_call(<procedure-number>)
+	#  <pack arguments using self.packer>
+	#  self.do_call()	# This does the actual message exchange
+	#  <unpack reply using self.unpacker>
+	#  self.end_call()
+	#  return <reply>
+	# If the call fails, an exception is raised by do_call().
+	# If the reply does not match what you unpack, an exception is
+	# raised either during unpacking (if you overrun the buffer)
+	# or by end_call() (if you leave values in the buffer).
+	# Calling packer methods with invalid arguments (e.g. if
+	# invalid arguments were passed from outside) will also result
+	# in exceptions during packing.
+
+	def Mnt(self, directory):
+		self.start_call(1)
+		self.packer.pack_string(directory)
+		self.do_call()
+		stat = self.unpacker.unpack_fhstatus()
+		self.end_call()
+		return stat
+
+	def Dump(self):
+		self.start_call(2)
+		self.do_call()
+		list = self.unpacker.unpack_mountlist()
+		self.end_call()
+		return list
+
+	def Umnt(self, directory):
+		self.start_call(3)
+		self.packer.pack_string(directory)
+		self.do_call()
+		self.end_call()
+
+	def Umntall(self):
+		self.start_call(4)
+		self.do_call()
+		self.end_call()
+
+	def Export(self):
+		self.start_call(5)
+		self.do_call()
+		list = self.unpacker.unpack_exportlist()
+		self.end_call()
+		return list
+
+
+# We turn the partial Mount client into a full one for either protocol
+# by use of multiple inheritance.  (In general, when class C has base
+# classes B1...Bn, if x is an instance of class C, methods of x are
+# searched first in C, then in B1, then in B2, ..., finally in Bn.)
+
+class TCPMountClient(PartialMountClient, TCPClient):
+
+	def init(self, host):
+		return TCPClient.init(self, host, MOUNTPROG, MOUNTVERS)
+
+
+class UDPMountClient(PartialMountClient, UDPClient):
+
+	def init(self, host):
+		return UDPClient.init(self, host, MOUNTPROG, MOUNTVERS)
+
+
+# A little test program for the Mount client.  This takes a host as
+# command line argument (default the local machine), prints its export
+# list, and attempt to mount and unmount each exported files system.
+
+def test():
+	import sys
+	if sys.argv[1:]: host = sys.argv[1]
+	else: host = ''
+	mcl = UDPMountClient().init(host)
+	list = mcl.Export()
+	for item in list:
+		print item
+		try:
+			mcl.Mnt(item[0])
+		except:
+			print 'Sorry'
+			continue
+		mcl.Umnt(item[0])
+	return
+
+#test()
diff --git a/Demo/rpc/nfsclient.py b/Demo/rpc/nfsclient.py
new file mode 100644
index 0000000..2260139
--- /dev/null
+++ b/Demo/rpc/nfsclient.py
@@ -0,0 +1,207 @@
+# NFS RPC client -- RFC 1094
+
+# (See mountclient.py for some hints on how to write RPC clients in
+# Python in general)
+
+from rpc import UDPClient, TCPClient
+from mountclient import FHSIZE, MountPacker, MountUnpacker
+
+NFS_PROGRAM = 100003
+NFS_VERSION = 2
+
+# enum stat
+NFS_OK = 0
+# (...many error values...)
+
+# enum ftype
+NFNON = 0
+NFREG = 1
+NFDIR = 2
+NFBLK = 3
+NFCHR = 4
+NFLNK = 5
+
+
+class NFSPacker(MountPacker):
+
+	def pack_sattrargs(self, sa):
+		file, attributes = sa
+		self.pack_fhandle(file)
+		self.pack_sattr(attributes)
+
+	def pack_sattr(self, sa):
+		mode, uid, gid, size, atime, mtime = sa
+		self.pack_uint(mode)
+		self.pack_uint(uid)
+		self.pack_uint(gid)
+		self.pack_uint(size)
+		self.pack_timeval(atime)
+		self.pack_timeval(mtime)
+
+	def pack_diropargs(self, da):
+		dir, name = da
+		self.pack_fhandle(dir)
+		self.pack_string(name)
+
+	def pack_readdirargs(self, ra):
+		dir, cookie, count = ra
+		self.pack_fhandle(dir)
+		self.pack_uint(cookie)
+		self.pack_uint(count)
+
+	def pack_timeval(self, tv):
+		secs, usecs = tv
+		self.pack_uint(secs)
+		self.pack_uint(usecs)
+
+
+class NFSUnpacker(MountUnpacker):
+
+	def unpack_readdirres(self):
+		status = self.unpack_enum()
+		if status == NFS_OK:
+			entries = self.unpack_list(self.unpack_entry)
+			eof = self.unpack_bool()
+			rest = (entries, eof)
+		else:
+			rest = None
+		return (status, rest)
+
+	def unpack_entry(self):
+		fileid = self.unpack_uint()
+		name = self.unpack_string()
+		cookie = self.unpack_uint()
+		return (fileid, name, cookie)
+
+	def unpack_diropres(self):
+		status = self.unpack_enum()
+		if status == NFS_OK:
+			fh = self.unpack_fhandle()
+			fa = self.unpack_fattr()
+			rest = (fh, fa)
+		else:
+			rest = None
+		return (status, rest)
+
+	def unpack_attrstat(self):
+		status = self.unpack_enum()
+		if status == NFS_OK:
+			attributes = self.unpack_fattr()
+		else:
+			attributes = None
+		return status, attributes
+
+	def unpack_fattr(self):
+              type = self.unpack_enum()
+              mode = self.unpack_uint()
+              nlink = self.unpack_uint()
+              uid = self.unpack_uint()
+              gid = self.unpack_uint()
+              size = self.unpack_uint()
+              blocksize = self.unpack_uint()
+              rdev = self.unpack_uint()
+              blocks = self.unpack_uint()
+              fsid = self.unpack_uint()
+              fileid = self.unpack_uint()
+              atime = self.unpack_timeval()
+              mtime = self.unpack_timeval()
+              ctime = self.unpack_timeval()
+	      return (type, mode, nlink, uid, gid, size, blocksize, \
+		      rdev, blocks, fsid, fileid, atime, mtime, ctime)
+
+	def unpack_timeval(self):
+		secs = self.unpack_uint()
+		usecs = self.unpack_uint()
+		return (secs, usecs)
+
+
+class NFSClient(UDPClient):
+
+	def init(self, host):
+		return UDPClient.init(self, host, NFS_PROGRAM, NFS_VERSION)
+
+	def addpackers(self):
+		self.packer = NFSPacker().init()
+		self.unpacker = NFSUnpacker().init('')
+
+	def Getattr(self, fh):
+		self.start_call(1)
+		self.packer.pack_fhandle(fh)
+		self.do_call()
+		as = self.unpacker.unpack_attrstat()
+		self.end_call()
+		return as
+
+	def Setattr(self, sa):
+		self.start_call(2)
+		self.packer.pack_sattrargs(sa)
+		self.do_call()
+		as = self.unpacker.unpack_attrstat()
+		self.end_call()
+		return as
+
+	# Root() is obsolete
+
+	def Lookup(self, da):
+		self.start_call(4)
+		self.packer.pack_diropargs(da)
+		self.do_call()
+		dr = self.unpacker.unpack_diropres()
+		self.end_call()
+		return dr
+
+	# ...
+
+	def Readdir(self, ra):
+		self.start_call(16)
+		self.packer.pack_readdirargs(ra)
+		self.do_call()
+		rr = self.unpacker.unpack_readdirres()
+		self.end_call()
+		return rr
+
+	# Shorthand to get the entire contents of a directory
+	def Listdir(self, dir):
+		list = []
+		ra = (dir, 0, 16)
+		while 1:
+			(status, rest) = self.Readdir(ra)
+			if status <> NFS_OK:
+				break
+			entries, eof = rest
+			last_cookie = None
+			for fileid, name, cookie in entries:
+				print (fileid, name, cookie) # XXX
+				list.append(fileid, name)
+				last_cookie = cookie
+			if eof or not last_cookie:
+				break
+			ra = (ra[0], last_cookie, ra[2])
+		return list
+
+	
+def test():
+	import sys
+	if sys.argv[1:]: host = sys.argv[1]
+	else: host = ''
+	if sys.argv[2:]: filesys = sys.argv[2]
+	else: filesys = None
+	from mountclient import UDPMountClient, TCPMountClient
+	mcl = TCPMountClient().init(host)
+	if filesys == None:
+		list = mcl.Export()
+		for item in list:
+			print item
+		return
+	sf = mcl.Mnt(filesys)
+	print sf
+	fh = sf[1]
+	if fh:
+		ncl = NFSClient().init(host)
+		as = ncl.Getattr(fh)
+		print as
+		list = ncl.Listdir(fh)
+		for item in list: print item
+		mcl.Unmnt(filesys)
+	
+test()
diff --git a/Demo/rpc/rpc.py b/Demo/rpc/rpc.py
new file mode 100644
index 0000000..ba3bd54
--- /dev/null
+++ b/Demo/rpc/rpc.py
@@ -0,0 +1,376 @@
+# Implement (a subset of) Sun RPC, version 2 -- RFC1057.
+
+import xdr
+import socket
+import os
+
+RPCVERSION = 2
+
+CALL = 0
+REPLY = 1
+
+AUTH_NULL = 0
+AUTH_UNIX = 1
+AUTH_SHORT = 2
+AUTH_DES = 3
+
+MSG_ACCEPTED = 0
+MSG_DENIED = 1
+
+SUCCESS = 0				# RPC executed successfully
+PROG_UNAVAIL  = 1			# remote hasn't exported program
+PROG_MISMATCH = 2			# remote can't support version #
+PROC_UNAVAIL  = 3			# program can't support procedure
+GARBAGE_ARGS  = 4			# procedure can't decode params
+
+RPC_MISMATCH = 0			# RPC version number != 2
+AUTH_ERROR = 1				# remote can't authenticate caller
+
+AUTH_BADCRED      = 1			# bad credentials (seal broken)
+AUTH_REJECTEDCRED = 2			# client must begin new session
+AUTH_BADVERF      = 3			# bad verifier (seal broken)
+AUTH_REJECTEDVERF = 4			# verifier expired or replayed
+AUTH_TOOWEAK      = 5			# rejected for security reasons
+
+
+class Packer(xdr.Packer):
+
+	def pack_auth(self, auth):
+		flavor, stuff = auth
+		self.pack_enum(flavor)
+		self.pack_opaque(stuff)
+
+	def pack_auth_unix(self, stamp, machinename, uid, gid, gids):
+		self.pack_uint(stamp)
+		self.pack_string(machinename)
+		self.pack_uint(uid)
+		self.pack_uint(gid)
+		self.pack_uint(len(gids))
+		for i in gids:
+			self.pack_uint(i)
+
+	def pack_callheader(self, xid, prog, vers, proc, cred, verf):
+		self.pack_uint(xid)
+		self.pack_enum(CALL)
+		self.pack_uint(RPCVERSION)
+		self.pack_uint(prog)
+		self.pack_uint(vers)
+		self.pack_uint(proc)
+		self.pack_auth(cred)
+		self.pack_auth(verf)
+		# Caller must add procedure-specific part of call
+
+	def pack_replyheader(self, xid, verf):
+		self.pack_uint(xid)
+		self.pack_enum(REPLY)
+		self.pack_uint(MSG_ACCEPTED)
+		self.pack_auth(verf)
+		self.pack_enum(SUCCESS)
+		# Caller must add procedure-specific part of reply
+
+
+class Unpacker(xdr.Unpacker):
+
+	def unpack_auth(self):
+		flavor = self.unpack_enum()
+		stuff = self.unpack_opaque()
+		return (flavor, stuff)
+
+	def unpack_replyheader(self):
+		xid = self.unpack_uint()
+		mtype = self.unpack_enum()
+		if mtype <> REPLY:
+			raise RuntimeError, 'no REPLY but ' + str(mtype)
+		stat = self.unpack_enum()
+		if stat <> MSG_ACCEPTED:
+			if stat == MSG_DENIED:
+				stat = self.unpack_enum()
+				if stat == RPC_MISMATCH:
+					low = self.unpack_uint()
+					high = self.unpack_uint()
+					raise 'RPC_MISMATCH', (low, high)
+				if stat == AUTH_ERROR:
+					stat = self.unpack_uint()
+					raise 'AUTH_ERROR', str(stat)
+				raise 'MSG_REJECTED', str(stat)
+			raise RuntimeError, 'no MSG_ACCEPTED but ' + str(stat)
+		verf = self.unpack_auth()
+		stat = self.unpack_enum()
+		if stat <> SUCCESS:
+			raise RuntimeError, 'no SUCCESS but ' + str(stat)
+		return xid, verf
+		# Caller must get procedure-specific part of reply
+
+
+# Common base class for clients
+
+class Client:
+
+	def init(self, host, prog, vers, port, type):
+		self.host = host
+		self.prog = prog
+		self.vers = vers
+		self.port = port
+		self.type = type
+		self.sock = socket.socket(socket.AF_INET, type)
+		self.sock.connect((host, port))
+		self.lastxid = 0
+		self.addpackers()
+		self.cred = None
+		self.verf = None
+		return self
+
+	def Null(self):			# Procedure 0 is always like this
+		self.start_call(0)
+		self.do_call(0)
+		self.end_call()
+
+	def close(self):
+		self.sock.close()
+
+	# Functions that may be overridden by specific derived classes
+
+	def addpackers(self):
+		self.packer = Packer().init()
+		self.unpacker = Unpacker().init('')
+
+	def mkcred(self, proc):
+		if self.cred == None:
+			p = Packer().init()
+			p.pack_auth_unix(0, socket.gethostname(), \
+				os.getuid(), os.getgid(), [])
+			self.cred = p.get_buf()
+		return (AUTH_UNIX, self.cred)
+
+	def mkverf(self, proc):
+		return (AUTH_NULL, '')
+
+
+# Record-Marking standard support
+
+def sendfrag(sock, last, frag):
+	x = len(frag)
+	if last: x = x | 0x80000000L
+	header = (chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
+		  chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
+	sock.send(header + frag)
+
+def sendrecord(sock, record):
+	sendfrag(sock, 1, record)
+
+def recvfrag(sock):
+	header = sock.recv(4)
+	x = long(ord(header[0]))<<24 | ord(header[1])<<16 | \
+	    ord(header[2])<<8 | ord(header[3])
+	last = ((x & 0x80000000) != 0)
+	n = int(x & 0x7fffffff)
+	frag = ''
+	while n > 0:
+		buf = sock.recv(n)
+		if not buf: raise EOFError
+		n = n - len(buf)
+		frag = frag + buf
+	return last, frag
+
+def recvrecord(sock):
+	record = ''
+	last = 0
+	while not last:
+		last, frag = recvfrag(sock)
+		record = record + frag
+	return record
+
+
+# Raw TCP-based client
+
+class RawTCPClient(Client):
+
+	def init(self, host, prog, vers, port):
+		return Client.init(self, host, prog, vers, port, \
+			socket.SOCK_STREAM)
+
+	def start_call(self, proc):
+		self.lastxid = xid = self.lastxid + 1
+		cred = self.mkcred(proc)
+		verf = self.mkverf(proc)
+		p = self.packer
+		p.reset()
+		p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
+
+	def do_call(self, *rest):
+		# rest is used for UDP buffer size; ignored for TCP
+		call = self.packer.get_buf()
+		sendrecord(self.sock, call)
+		reply = recvrecord(self.sock)
+		u = self.unpacker
+		u.reset(reply)
+		xid, verf = u.unpack_replyheader()
+		if xid <> self.lastxid:
+			# Can't really happen since this is TCP...
+			raise RuntimeError, 'wrong xid in reply ' + `xid` + \
+				' instead of ' + `self.lastxid`
+
+	def end_call(self):
+		self.unpacker.done()
+
+
+# Raw UDP-based client
+# XXX This class does not recover from missed/duplicated packets!
+
+class RawUDPClient(Client):
+
+	def init(self, host, prog, vers, port):
+		return Client.init(self, host, prog, vers, port, \
+			socket.SOCK_DGRAM)
+
+	def start_call(self, proc):
+		self.lastxid = xid = self.lastxid + 1
+		cred = self.mkcred(proc)
+		verf = self.mkverf(proc)
+		p = self.packer
+		p.reset()
+		p.pack_callheader(xid, self.prog, self.vers, proc, cred, verf)
+
+	def do_call(self, *rest):
+		if len(rest) == 0:
+			bufsize = 8192
+		elif len(rest) > 1:
+			raise TypeError, 'too many args'
+		else:
+			bufsize = rest[0] + 512
+		call = self.packer.get_buf()
+		self.sock.send(call)
+		# XXX What about time-out and retry?
+		reply = self.sock.recv(bufsize)
+		u = self.unpacker
+		u.reset(reply)
+		xid, verf = u.unpack_replyheader()
+		if xid <> self.lastxid:
+			# XXX Should assume it's an old reply
+			raise RuntimeError, 'wrong xid in reply ' + `xid` + \
+				' instead of ' + `self.lastxid`
+
+	def end_call(self):
+		self.unpacker.done()
+
+
+# Port mapper interface
+
+PMAP_PORT = 111
+PMAP_PROG = 100000
+PMAP_VERS = 2
+PMAPPROC_NULL = 0			# (void) -> void
+PMAPPROC_SET = 1			# (mapping) -> bool
+PMAPPROC_UNSET = 2			# (mapping) -> bool
+PMAPPROC_GETPORT = 3			# (mapping) -> unsigned int
+PMAPPROC_DUMP = 4			# (void) -> pmaplist
+PMAPPROC_CALLIT = 5			# (call_args) -> call_result
+
+# A mapping is (prog, vers, prot, port) and prot is one of:
+
+IPPROTO_TCP = 6
+IPPROTO_UDP = 17
+
+# A pmaplist is a variable-length list of mappings, as follows:
+# either (1, mapping, pmaplist) or (0).
+
+# A call_args is (prog, vers, proc, args) where args is opaque;
+# a call_result is (port, res) where res is opaque.
+
+
+class PortMapperPacker(Packer):
+
+	def pack_mapping(self, mapping):
+		prog, vers, prot, port = mapping
+		self.pack_uint(prog)
+		self.pack_uint(vers)
+		self.pack_uint(prot)
+		self.pack_uint(port)
+
+	def pack_pmaplist(self, list):
+		self.pack_list(list, self.pack_mapping)
+
+
+class PortMapperUnpacker(Unpacker):
+
+	def unpack_mapping(self):
+		prog = self.unpack_uint()
+		vers = self.unpack_uint()
+		prot = self.unpack_uint()
+		port = self.unpack_uint()
+		return prog, vers, prot, port
+
+	def unpack_pmaplist(self):
+		return self.unpack_list(self.unpack_mapping)
+
+
+class PartialPortMapperClient:
+
+	def addpackers(self):
+		self.packer = PortMapperPacker().init()
+		self.unpacker = PortMapperUnpacker().init('')
+
+	def Getport(self, mapping):
+		self.start_call(PMAPPROC_GETPORT)
+		self.packer.pack_mapping(mapping)
+		self.do_call(4)
+		port = self.unpacker.unpack_uint()
+		self.end_call()
+		return port
+
+	def Dump(self):
+		self.start_call(PMAPPROC_DUMP)
+		self.do_call(8192-512)
+		list = self.unpacker.unpack_pmaplist()
+		self.end_call()
+		return list
+
+
+class TCPPortMapperClient(PartialPortMapperClient, RawTCPClient):
+
+	def init(self, host):
+		return RawTCPClient.init(self, \
+			host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
+
+
+class UDPPortMapperClient(PartialPortMapperClient, RawUDPClient):
+
+	def init(self, host):
+		return RawUDPClient.init(self, \
+			host, PMAP_PROG, PMAP_VERS, PMAP_PORT)
+
+
+class TCPClient(RawTCPClient):
+
+	def init(self, host, prog, vers):
+		pmap = TCPPortMapperClient().init(host)
+		port = pmap.Getport((prog, vers, IPPROTO_TCP, 0))
+		pmap.close()
+		return RawTCPClient.init(self, host, prog, vers, port)
+
+
+class UDPClient(RawUDPClient):
+
+	def init(self, host, prog, vers):
+		pmap = UDPPortMapperClient().init(host)
+		port = pmap.Getport((prog, vers, IPPROTO_UDP, 0))
+		pmap.close()
+		return RawUDPClient.init(self, host, prog, vers, port)
+
+
+def test():
+	import T
+	T.TSTART()
+	pmap = UDPPortMapperClient().init('')
+	T.TSTOP()
+	pmap.Null()
+	T.TSTOP()
+	list = pmap.Dump()
+	T.TSTOP()
+	list.sort()
+	for prog, vers, prot, port in list:
+		print prog, vers,
+		if prot == IPPROTO_TCP: print 'tcp',
+		elif prot == IPPROTO_UDP: print 'udp',
+		else: print prot,
+		print port
diff --git a/Demo/rpc/xdr.py b/Demo/rpc/xdr.py
new file mode 100644
index 0000000..b189b45
--- /dev/null
+++ b/Demo/rpc/xdr.py
@@ -0,0 +1,141 @@
+# Implement (a subset of) Sun XDR -- RFC1014.
+
+
+import struct
+
+
+class Packer:
+
+	def init(self):
+		self.reset()
+		return self
+
+	def reset(self):
+		self.buf = ''
+
+	def get_buf(self):
+		return self.buf
+
+	def pack_uint(self, x):
+		self.buf = self.buf + \
+			(chr(int(x>>24 & 0xff)) + chr(int(x>>16 & 0xff)) + \
+			 chr(int(x>>8 & 0xff)) + chr(int(x & 0xff)))
+	if struct.pack('i', 1) == '\0\0\0\1':
+		def pack_uint(self, x):
+			self.buf = self.buf + struct.pack('i', x)
+
+	pack_int = pack_uint
+
+	pack_enum = pack_int
+
+	def pack_bool(self, x):
+		if x: self.buf = self.buf + '\0\0\0\1'
+		else: self.buf = self.buf + '\0\0\0\0'
+
+	def pack_uhyper(self, x):
+		self.pack_uint(x>>32 & 0xffffffff)
+		self.pack_uint(x & 0xffffffff)
+
+	pack_hyper = pack_uhyper
+
+	def pack_fstring(self, n, s):
+		if n < 0:
+			raise ValueError, 'fstring size must be nonnegative'
+		n = ((n+3)/4)*4
+		data = s[:n]
+		data = data + (n - len(data)) * '\0'
+		self.buf = self.buf + data
+
+	pack_fopaque = pack_fstring
+
+	def pack_string(self, s):
+		n = len(s)
+		self.pack_uint(n)
+		self.pack_fstring(n, s)
+
+	pack_opaque = pack_string
+
+	def pack_list(self, list, pack_item):
+		for item in list:
+			self.pack_uint(1)
+			pack_item(list)
+		self.pack_uint(0)
+
+
+class Unpacker:
+
+	def init(self, data):
+		self.reset(data)
+		return self
+
+	def reset(self, data):
+		self.buf = data
+		self.pos = 0
+
+	def done(self):
+		if self.pos < len(self.buf):
+			raise RuntimeError, 'unextracted data remains'
+
+	def unpack_uint(self):
+		i = self.pos
+		self.pos = j = i+4
+		data = self.buf[i:j]
+		x = long(ord(data[0]))<<24 | ord(data[1])<<16 | \
+			ord(data[2])<<8 | ord(data[3])
+		# Return a Python long only if the value is not representable
+		# as a nonnegative Python int
+		if x < 0x80000000L: x = int(x)
+		return x
+	if struct.unpack('i', '\0\0\0\1') == 1:
+		def unpack_uint(self):
+			i = self.pos
+			self.pos = j = i+4
+			return struct.unpack('i', self.buf[i:j])
+
+	def unpack_int(self):
+		x = self.unpack_uint()
+		if x >= 0x80000000L: x = x - 0x100000000L
+		return int(x)
+
+	unpack_enum = unpack_int
+
+	unpack_bool = unpack_int
+
+	def unpack_uhyper(self):
+		hi = self.unpack_uint()
+		lo = self.unpack_uint()
+		return long(hi)<<32 | lo
+
+	def unpack_hyper(self):
+		x = self.unpack_uhyper()
+		if x >= 0x8000000000000000L: x = x - 0x10000000000000000L
+		return x
+
+	def unpack_fstring(self, n):
+		if n < 0:
+			raise ValueError, 'fstring size must be nonnegative'
+		i = self.pos
+		j = i + (n+3)/4*4
+		if j > len(self.buf):
+			raise RuntimeError, 'buffer overrun'
+		self.pos = j
+		return self.buf[i:i+n]
+
+	unpack_fopaque = unpack_fstring
+
+	def unpack_string(self):
+		n = self.unpack_uint()
+		return self.unpack_fstring(n)
+
+	unpack_opaque = unpack_string
+
+	def unpack_list(self, unpack_item):
+		list = []
+		while 1:
+			x = self.unpack_uint()
+			if not x: break
+			if x <> 1:
+				raise RuntimeError, \
+					'0 or 1 expected, got ' + `x`
+			list.append(unpack_item())
+		return list