shithub: purgatorio

ref: bd6c2aad586814b091ce5aca9d41cf2c51adb37b
dir: /appl/lib/ninep.b/

View raw version
implement Ninep;

include "sys.m";
	sys: Sys;

include "9p.m";

STR: con BIT16SZ;	# string length
TAG: con BIT16SZ;
FID: con BIT32SZ;
QID: con BIT8SZ+BIT32SZ+BIT64SZ;
LEN: con BIT16SZ;	# stat and qid array lengths
COUNT: con BIT32SZ;
OFFSET: con BIT64SZ;

H: con BIT32SZ+BIT8SZ+BIT16SZ;	# minimum header length: size[4] type tag[2]

#
# the following array could be shorter if it were indexed by (type-Tversion)
#
hdrlen := array[Tmax] of
{
Tversion =>	H+COUNT+STR,	# size[4] Tversion tag[2] msize[4] version[s]
Rversion =>	H+COUNT+STR,	# size[4] Rversion tag[2] msize[4] version[s]

Tauth =>	H+FID+STR+STR,		# size[4] Tauth tag[2] afid[4] uname[s] aname[s]
Rauth =>	H+QID,			# size[4] Rauth tag[2] aqid[13]

Rerror =>	H+STR,		# size[4] Rerror tag[2] ename[s]

Tflush =>	H+TAG,		# size[4] Tflush tag[2] oldtag[2]
Rflush =>	H,			# size[4] Rflush tag[2]

Tattach =>	H+FID+FID+STR+STR,	# size[4] Tattach tag[2] fid[4] afid[4] uname[s] aname[s]
Rattach =>	H+QID,		# size[4] Rattach tag[2] qid[13]

Twalk =>	H+FID+FID+LEN,	# size[4] Twalk tag[2] fid[4] newfid[4] nwname[2] nwname*(wname[s])
Rwalk =>	H+LEN,		# size[4] Rwalk tag[2] nwqid[2] nwqid*(wqid[13])

Topen =>	H+FID+BIT8SZ,		# size[4] Topen tag[2] fid[4] mode[1]
Ropen =>	H+QID+COUNT,	# size[4] Ropen tag[2] qid[13] iounit[4]

Tcreate =>	H+FID+STR+BIT32SZ+BIT8SZ,	# size[4] Tcreate tag[2] fid[4] name[s] perm[4] mode[1]
Rcreate =>	H+QID+COUNT,	# size[4] Rcreate tag[2] qid[13] iounit[4]

Tread =>	H+FID+OFFSET+COUNT,	# size[4] Tread tag[2] fid[4] offset[8] count[4]
Rread =>	H+COUNT,		# size[4] Rread tag[2] count[4] data[count]

Twrite =>	H+FID+OFFSET+COUNT,	# size[4] Twrite tag[2] fid[4] offset[8] count[4] data[count]
Rwrite =>	H+COUNT,	# size[4] Rwrite tag[2] count[4]

Tclunk =>	H+FID,	# size[4] Tclunk tag[2] fid[4]
Rclunk =>	H,		# size[4] Rclunk tag[2]

Tremove =>	H+FID,	# size[4] Tremove tag[2] fid[4]
Rremove =>	H,	# size[4] Rremove tag[2]

Tstat =>	H+FID,	# size[4] Tstat tag[2] fid[4]
Rstat =>	H+LEN,	# size[4] Rstat tag[2] stat[n]

Twstat =>	H+FID+LEN,	# size[4] Twstat tag[2] fid[4] stat[n]
Rwstat =>	H,	# size[4] Rwstat tag[2]
};

init()
{
	sys = load Sys Sys->PATH;
}

utflen(s: string): int
{
	# the domain is 16-bit unicode only, which is all that Inferno now implements
	n := l := len s;
	for(i:=0; i<l; i++)
		if((c := s[i]) > 16r7F){
			n++;
			if(c > 16r7FF)
				n++;
		}
	return n;
}

packdirsize(d: Sys->Dir): int
{
	return STATFIXLEN+utflen(d.name)+utflen(d.uid)+utflen(d.gid)+utflen(d.muid);
}

packdir(f: Sys->Dir): array of byte
{
	ds := packdirsize(f);
	a := array[ds] of byte;
	# size[2]
	a[0] = byte (ds-LEN);
	a[1] = byte ((ds-LEN)>>8);
	# type[2]
	a[2] = byte f.dtype;
	a[3] = byte (f.dtype>>8);
	# dev[4]
	a[4] = byte f.dev;
	a[5] = byte (f.dev>>8);
	a[6] = byte (f.dev>>16);
	a[7] = byte (f.dev>>24);
	# qid.type[1]
	# qid.vers[4]
	# qid.path[8]
	pqid(a, 8, f.qid);
	# mode[4]
	a[21] = byte f.mode;
	a[22] = byte (f.mode>>8);
	a[23] = byte (f.mode>>16);
	a[24] = byte (f.mode>>24);
	# atime[4]
	a[25] = byte f.atime;
	a[26] = byte (f.atime>>8);
	a[27] = byte (f.atime>>16);
	a[28] = byte (f.atime>>24);
	# mtime[4]
	a[29] = byte f.mtime;
	a[30] = byte (f.mtime>>8);
	a[31] = byte (f.mtime>>16);
	a[32] = byte (f.mtime>>24);
	# length[8]
	p64(a, 33, big f.length);
	# name[s]
	i := pstring(a, 33+BIT64SZ, f.name);
	i = pstring(a, i, f.uid);
	i = pstring(a, i, f.gid);
	i = pstring(a, i, f.muid);
	if(i != len a)
		raise "assertion: Ninep->packdir: bad count";	# can't happen unless packedsize is wrong
	return a;
}

pqid(a: array of byte, o: int, q: Sys->Qid): int
{
	a[o] = byte q.qtype;
	v := q.vers;
	a[o+1] = byte v;
	a[o+2] = byte (v>>8);
	a[o+3] = byte (v>>16);
	a[o+4] = byte (v>>24);
	v = int q.path;
	a[o+5] = byte v;
	a[o+6] = byte (v>>8);
	a[o+7] = byte (v>>16);
	a[o+8] = byte (v>>24);
	v = int (q.path >> 32);
	a[o+9] = byte v;
	a[o+10] = byte (v>>8);
	a[o+11] = byte (v>>16);
	a[o+12] = byte (v>>24);
	return o+QID;
}

pstring(a: array of byte, o: int, s: string): int
{
	sa := array of byte s;	# could do conversion ourselves
	n := len sa;
	a[o] = byte n;
	a[o+1] = byte (n>>8);
	a[o+2:] = sa;
	return o+LEN+n;
}

p32(a: array of byte, o: int, v: int): int
{
	a[o] = byte v;
	a[o+1] = byte (v>>8);
	a[o+2] = byte (v>>16);
	a[o+3] = byte (v>>24);
	return o+BIT32SZ;
}

p64(a: array of byte, o: int, b: big): int
{
	i := int b;
	a[o] = byte i;
	a[o+1] = byte (i>>8);
	a[o+2] = byte (i>>16);
	a[o+3] = byte (i>>24);
	i = int (b>>32);
	a[o+4] = byte i;
	a[o+5] = byte (i>>8);
	a[o+6] = byte (i>>16);
	a[o+7] = byte (i>>24);
	return o+BIT64SZ;
}

unpackdir(a: array of byte): (int, Sys->Dir)
{
	dir: Sys->Dir;

	if(len a < STATFIXLEN)
		return (0, dir);
	# size[2]
	sz := ((int a[1] << 8) | int a[0])+LEN;	# bytes this packed dir should occupy
	if(len a < sz)
		return (0, dir);
	# type[2]
	dir.dtype = (int a[3]<<8) | int a[2];
	# dev[4]
	dir.dev = (((((int a[7] << 8) | int a[6]) << 8) | int a[5]) << 8) | int a[4];
	# qid.type[1]
	# qid.vers[4]
	# qid.path[8]
	dir.qid = gqid(a, 8);
	# mode[4]
	dir.mode = (((((int a[24] << 8) | int a[23]) << 8) | int a[22]) << 8) | int a[21];
	# atime[4]
	dir.atime = (((((int a[28] << 8) | int a[27]) << 8) | int a[26]) << 8) | int a[25];
	# mtime[4]
	dir.mtime = (((((int a[32] << 8) | int a[31]) << 8) | int a[30]) << 8) | int a[29];
	# length[8]
	v0 := (((((int a[36] << 8) | int a[35]) << 8) | int a[34]) << 8) | int a[33];
	v1 := (((((int a[40] << 8) | int a[39]) << 8) | int a[38]) << 8) | int a[37];
	dir.length = (big v1 << 32) | (big v0 & 16rFFFFFFFF);
	# name[s], uid[s], gid[s], muid[s]
	i: int;
	(dir.name, i) = gstring(a, 41);
	(dir.uid, i) = gstring(a, i);
	(dir.gid, i) = gstring(a, i);
	(dir.muid, i) = gstring(a, i);
	if(i != sz)
		return (0, dir);
	return (i, dir);
}

gqid(f: array of byte, i: int): Sys->Qid
{
	qtype := int f[i];
	vers := (((((int f[i+4] << 8) | int f[i+3]) << 8) | int f[i+2]) << 8) | int f[i+1];
	i += BIT8SZ+BIT32SZ;
	path0 := (((((int f[i+3] << 8) | int f[i+2]) << 8) | int f[i+1]) << 8) | int f[i];
	i += BIT32SZ;
	path1 := (((((int f[i+3] << 8) | int f[i+2]) << 8) | int f[i+1]) << 8) | int f[i];
	path := (big path1 << 32) | (big path0 & 16rFFFFFFFF);
	return (path, vers, qtype);
}

g32(f: array of byte, i: int): int
{
	return (((((int f[i+3] << 8) | int f[i+2]) << 8) | int f[i+1]) << 8) | int f[i];
}

g64(f: array of byte, i: int): big
{
	b0 := (((((int f[i+3] << 8) | int f[i+2]) << 8) | int f[i+1]) << 8) | int f[i];
	b1 := (((((int f[i+7] << 8) | int f[i+6]) << 8) | int f[i+5]) << 8) | int f[i+4];
	return (big b1 << 32) | (big b0 & 16rFFFFFFFF);
}

gstring(a: array of byte, o: int): (string, int)
{
	if(o < 0 || o+STR > len a)
		return (nil, -1);
	l := (int a[o+1] << 8) | int a[o];
	o += STR;
	e := o+l;
	if(e > len a)
		return (nil, -1);
	return (string a[o:e], e);
}

ttag2type := array[] of {
tagof Tmsg.Readerror => 0,
tagof Tmsg.Version => Tversion,
tagof Tmsg.Auth => Tauth,
tagof Tmsg.Attach => Tattach,
tagof Tmsg.Flush => Tflush,
tagof Tmsg.Walk => Twalk,
tagof Tmsg.Open => Topen,
tagof Tmsg.Create => Tcreate,
tagof Tmsg.Read => Tread,
tagof Tmsg.Write => Twrite,
tagof Tmsg.Clunk => Tclunk,
tagof Tmsg.Stat => Tstat,
tagof Tmsg.Remove => Tremove,
tagof Tmsg.Wstat => Twstat,
};

Tmsg.mtype(t: self ref Tmsg): int
{
	return ttag2type[tagof t];
}

Tmsg.packedsize(t: self ref Tmsg): int
{
	mtype := ttag2type[tagof t];
	if(mtype <= 0)
		return 0;
	ml := hdrlen[mtype];
	pick m := t {
	Version =>
		ml += utflen(m.version);
	Auth =>
		ml += utflen(m.uname)+utflen(m.aname);
	Attach =>
		ml += utflen(m.uname)+utflen(m.aname);
	Walk =>
		for(i:=0; i<len m.names; i++)
			ml += STR+utflen(m.names[i]);
	Create =>
		ml += utflen(m.name);
	Write =>
		ml += len m.data;
	Wstat =>
		ml += packdirsize(m.stat);
	}
	return ml;
}

Tmsg.pack(t: self ref Tmsg): array of byte
{
	if(t == nil)
		return nil;
	ds := t.packedsize();
	if(ds <= 0)
		return nil;
	d := array[ds] of byte;
	d[0] = byte ds;
	d[1] = byte (ds>>8);
	d[2] = byte (ds>>16);
	d[3] = byte (ds>>24);
	d[4] = byte ttag2type[tagof t];
	d[5] = byte t.tag;
	d[6] = byte (t.tag >> 8);
	pick m := t {
	Version =>
		p32(d, H, m.msize);
		pstring(d, H+COUNT, m.version);
	Auth =>
		p32(d, H, m.afid);
		o := pstring(d, H+FID, m.uname);
		pstring(d, o, m.aname);
	Flush =>
		v := m.oldtag;
		d[H] = byte v;
		d[H+1] = byte (v>>8);
	Attach =>
		p32(d, H, m.fid);
		p32(d, H+FID, m.afid);
		o := pstring(d, H+2*FID, m.uname);
		pstring(d, o, m.aname);
	Walk =>
		d[H] = byte m.fid;
		d[H+1] = byte (m.fid>>8);
		d[H+2] = byte (m.fid>>16);
		d[H+3] = byte (m.fid>>24);
		d[H+FID] = byte m.newfid;
		d[H+FID+1] = byte (m.newfid>>8);
		d[H+FID+2] = byte (m.newfid>>16);
		d[H+FID+3] = byte (m.newfid>>24);
		n := len m.names;
		d[H+2*FID] = byte n;
		d[H+2*FID+1] = byte (n>>8);
		o := H+2*FID+LEN;
		for(i := 0; i < n; i++)
			o = pstring(d, o, m.names[i]);
	Open =>
		p32(d, H, m.fid);
		d[H+FID] = byte m.mode;
	Create =>
		p32(d, H, m.fid);
		o := pstring(d, H+FID, m.name);
		p32(d, o, m.perm);
		d[o+BIT32SZ] = byte m.mode;
	Read =>
		p32(d, H, m.fid);
		p64(d, H+FID, m.offset);
		p32(d, H+FID+OFFSET, m.count);
	Write =>
		p32(d, H, m.fid);
		p64(d, H+FID, m.offset);
		n := len m.data;
		p32(d, H+FID+OFFSET, n);
		d[H+FID+OFFSET+COUNT:] = m.data;
	Clunk or Remove or Stat =>
		p32(d, H, m.fid);
	Wstat =>
		p32(d, H, m.fid);
		stat := packdir(m.stat);
		n := len stat;
		d[H+FID] = byte n;
		d[H+FID+1] = byte (n>>8);
		d[H+FID+LEN:] = stat;
	* =>
		raise sys->sprint("assertion: Ninep->Tmsg.pack: bad tag: %d", tagof t);
	}
	return d;
}

Tmsg.unpack(f: array of byte): (int, ref Tmsg)
{
	if(len f < H)
		return (0, nil);
	size := (int f[1] << 8) | int f[0];
	size |= ((int f[3] << 8) | int f[2]) << 16;
	if(len f != size){
		if(len f < size)
			return (0, nil);	# need more data
		f = f[0:size];	# trim to exact length
	}
	mtype := int f[4];
	if(mtype >= len hdrlen || (mtype&1) != 0 || size < hdrlen[mtype])
		return (-1, nil);

	tag := (int f[6] << 8) | int f[5];
	fid := 0;
	if(hdrlen[mtype] >= H+FID)
		fid = g32(f, H);	# fid is always in same place: extract it once for all if there

	# return out of each case body for a legal message;
	# break out of the case for an illegal one

Decode:
	case mtype {
	* =>
		sys->print("styx: Tmsg.unpack: bad type %d\n", mtype);
	Tversion =>
		msize := fid;
		(version, o) := gstring(f, H+COUNT);
		if(o <= 0)
			break;
		return (o, ref Tmsg.Version(tag, msize, version));
	Tauth =>
		(uname, o1) := gstring(f, H+FID);
		(aname, o2) := gstring(f, o1);
		if(o2 <= 0)
			break;
		return (o2, ref Tmsg.Auth(tag, fid, uname, aname));
	Tflush =>
		oldtag := (int f[H+1] << 8) | int f[H];
		return (H+TAG, ref Tmsg.Flush(tag, oldtag));
	Tattach =>
		afid := g32(f, H+FID);
		(uname, o1) := gstring(f, H+2*FID);
		(aname, o2) := gstring(f, o1);
		if(o2 <= 0)
			break;
		return (o2, ref Tmsg.Attach(tag, fid, afid, uname, aname));
	Twalk =>
		newfid := g32(f, H+FID);
		n := (int f[H+2*FID+1] << 8) | int f[H+2*FID];
		if(n > MAXWELEM)
			break;
		o := H+2*FID+LEN;
		names: array of string = nil;
		if(n > 0){
			names = array[n] of string;
			for(i:=0; i<n; i++){
				(names[i], o) = gstring(f, o);
				if(o <= 0)
					break Decode;
			}
		}
		return (o, ref Tmsg.Walk(tag, fid, newfid, names));
	Topen =>
		return (H+FID+BIT8SZ, ref Tmsg.Open(tag, fid, int f[H+FID]));
	Tcreate =>
		(name, o) := gstring(f, H+FID);
		if(o <= 0 || o+BIT32SZ+BIT8SZ > len f)
			break;
		perm := g32(f, o);
		o += BIT32SZ;
		mode := int f[o++];
		return (o, ref Tmsg.Create(tag, fid, name, perm, mode));
	Tread =>
		offset := g64(f, H+FID);
		count := g32(f, H+FID+OFFSET);
		return (H+FID+OFFSET+COUNT, ref Tmsg.Read(tag, fid, offset, count));
	Twrite =>
		offset := g64(f, H+FID);
		count := g32(f, H+FID+OFFSET);
		O: con H+FID+OFFSET+COUNT;
		if(count > len f-O)
			break;
		data := f[O:O+count];
		return (O+count, ref Tmsg.Write(tag, fid, offset, data));
	Tclunk =>
		return (H+FID, ref Tmsg.Clunk(tag, fid));
	Tremove =>
		return (H+FID, ref Tmsg.Remove(tag, fid));
	Tstat =>
		return (H+FID, ref Tmsg.Stat(tag, fid));
	Twstat =>
		n := int (f[H+FID+1]<<8) | int f[H+FID];
		if(len f < H+FID+LEN+n)
			break;
		(ds, stat) := unpackdir(f[H+FID+LEN:]);
		if(ds != n){
			sys->print("Ninep->Tmsg.unpack: wstat count: %d/%d\n", ds, n);	# temporary
			break;
		}
		return (H+FID+LEN+n, ref Tmsg.Wstat(tag, fid, stat));
	}
	return (-1, nil);		# illegal
}

tmsgname := array[] of {
tagof Tmsg.Readerror => "Readerror",
tagof Tmsg.Version => "Version",
tagof Tmsg.Auth => "Auth",
tagof Tmsg.Attach => "Attach",
tagof Tmsg.Flush => "Flush",
tagof Tmsg.Walk => "Walk",
tagof Tmsg.Open => "Open",
tagof Tmsg.Create => "Create",
tagof Tmsg.Read => "Read",
tagof Tmsg.Write => "Write",
tagof Tmsg.Clunk => "Clunk",
tagof Tmsg.Stat => "Stat",
tagof Tmsg.Remove => "Remove",
tagof Tmsg.Wstat => "Wstat",
};

Tmsg.text(t: self ref Tmsg): string
{
	if(t == nil)
		return "nil";
	s := sys->sprint("Tmsg.%s(%ud", tmsgname[tagof t], t.tag);
	pick m:= t {
	* =>
		return s + ",ILLEGAL)";
	Readerror =>
		return s + sys->sprint(",\"%s\")", m.error);
	Version =>
		return s + sys->sprint(",%d,\"%s\")", m.msize, m.version);
	Auth =>
		return s + sys->sprint(",%ud,\"%s\",\"%s\")", m.afid, m.uname, m.aname);
	Flush =>
		return s + sys->sprint(",%ud)", m.oldtag);
	Attach =>
		return s + sys->sprint(",%ud,%ud,\"%s\",\"%s\")", m.fid, m.afid, m.uname, m.aname);
	Walk =>
		s += sys->sprint(",%ud,%ud", m.fid, m.newfid);
		if(len m.names != 0){
			s += ",array[] of {";
			for(i := 0; i < len m.names; i++){
				c := ",";
				if(i == 0)
					c = "";
				s += sys->sprint("%s\"%s\"", c, m.names[i]);
			}
			s += "}";
		}else
			s += ",nil";
		return s + ")";
	Open =>
		return s + sys->sprint(",%ud,%d)", m.fid, m.mode);
	Create =>
		return s + sys->sprint(",%ud,\"%s\",8r%uo,%d)", m.fid, m.name, m.perm, m.mode);
	Read =>
		return s + sys->sprint(",%ud,%bd,%ud)", m.fid, m.offset, m.count);
	Write =>
		return s + sys->sprint(",%ud,%bd,array[%d] of byte)", m.fid, m.offset, len m.data);
	Clunk or
	Remove or
	Stat =>
		return s + sys->sprint(",%ud)", m.fid);
	Wstat =>
		return s + sys->sprint(",%ud,%s)", m.fid, dir2text(m.stat));
	}
}

Tmsg.read(fd: ref Sys->FD, msglim: int): ref Tmsg
{
	(msg, err) := readmsg(fd, msglim);
	if(err != nil)
		return ref Tmsg.Readerror(0, err);
	if(msg == nil)
		return nil;
	(nil, m) := Tmsg.unpack(msg);
	if(m == nil)
		return ref Tmsg.Readerror(0, "bad 9P T-message format");
	return m;
}

rtag2type := array[] of {
tagof Rmsg.Version	=> Rversion,
tagof Rmsg.Auth	=> Rauth,
tagof Rmsg.Error	=> Rerror,
tagof Rmsg.Flush	=> Rflush,
tagof Rmsg.Attach	=> Rattach,
tagof Rmsg.Walk	=> Rwalk,
tagof Rmsg.Open	=> Ropen,
tagof Rmsg.Create	=> Rcreate,
tagof Rmsg.Read	=> Rread,
tagof Rmsg.Write	=> Rwrite,
tagof Rmsg.Clunk	=> Rclunk,
tagof Rmsg.Remove	=> Rremove,
tagof Rmsg.Stat	=> Rstat,
tagof Rmsg.Wstat	=> Rwstat,
};

Rmsg.mtype(r: self ref Rmsg): int
{
	return rtag2type[tagof r];
}

Rmsg.packedsize(r: self ref Rmsg): int
{
	mtype := rtag2type[tagof r];
	if(mtype <= 0)
		return 0;
	ml := hdrlen[mtype];
	pick m := r {
	Version =>
		ml += utflen(m.version);
	Error =>
		ml += utflen(m.ename);
	Walk =>
		ml += QID*len m.qids;
	Read =>
		ml += len m.data;
	Stat =>
		ml += packdirsize(m.stat);
	}
	return ml;
}

Rmsg.pack(r: self ref Rmsg): array of byte
{
	if(r == nil)
		return nil;
	ps := r.packedsize();
	if(ps <= 0)
		return nil;
	d := array[ps] of byte;
	d[0] = byte ps;
	d[1] = byte (ps>>8);
	d[2] = byte (ps>>16);
	d[3] = byte (ps>>24);
	d[4] = byte rtag2type[tagof r];
	d[5] = byte r.tag;
	d[6] = byte (r.tag >> 8);
	pick m := r {
	Version =>
		p32(d, H, m.msize);
		pstring(d, H+BIT32SZ, m.version);
	Auth =>
		pqid(d, H, m.aqid);
	Flush or
	Clunk or
	Remove or
	Wstat =>
		;	# nothing more required
	Error	=>
		pstring(d, H, m.ename);
	Attach =>
		pqid(d, H, m.qid);
	Walk =>
		n := len m.qids;
		d[H] = byte n;
		d[H+1] = byte (n>>8);
		o := H+LEN;
		for(i:=0; i<n; i++){
			pqid(d, o, m.qids[i]);
			o += QID;
		}
	Create or
	Open =>
		pqid(d, H, m.qid);
		p32(d, H+QID, m.iounit);
	Read =>
		v := len m.data;
		d[H] = byte v;
		d[H+1] = byte (v>>8);
		d[H+2] = byte (v>>16);
		d[H+3] = byte (v>>24);
		d[H+4:] = m.data;
	Write =>
		v := m.count;
		d[H] = byte v;
		d[H+1] = byte (v>>8);
		d[H+2] = byte (v>>16);
		d[H+3] = byte (v>>24);
	Stat =>
		stat := packdir(m.stat);
		v := len stat;
		d[H] = byte v;
		d[H+1] = byte (v>>8);
		d[H+2:] = stat;		# should avoid copy?
	* =>
		raise sys->sprint("assertion: Ninep->Rmsg.pack: missed case: tag %d", tagof r);
	}
	return d;
}

Rmsg.unpack(f: array of byte): (int, ref Rmsg)
{
	if(len f < H)
		return (0, nil);
	size := (int f[1] << 8) | int f[0];
	size |= ((int f[3] << 8) | int f[2]) << 16;	# size includes itself
	if(len f != size){
		if(len f < size)
			return (0, nil);	# need more data
		f = f[0:size];	# trim to exact length
	}
	mtype := int f[4];
	if(mtype >= len hdrlen || (mtype&1) == 0 || size < hdrlen[mtype])
		return (-1, nil);

	tag := (int f[6] << 8) | int f[5];

	# return out of each case body for a legal message;
	# break out of the case for an illegal one

	case mtype {
	* =>
		sys->print("Ninep->Rmsg.unpack: bad type %d\n", mtype);	# temporary
	Rversion =>
		msize := g32(f, H);
		(version, o) := gstring(f, H+BIT32SZ);
		if(o <= 0)
			break;
		return (o, ref Rmsg.Version(tag, msize, version));
	Rauth =>
		return (H+QID, ref Rmsg.Auth(tag, gqid(f, H)));
	Rflush =>
		return (H, ref Rmsg.Flush(tag));
	Rerror =>
		(ename, o) := gstring(f, H);
		if(o <= 0)
			break;
		return (o, ref Rmsg.Error(tag, ename));
	Rclunk =>
		return (H, ref Rmsg.Clunk(tag));
	Rremove =>
		return (H, ref Rmsg.Remove(tag));
	Rwstat=>
		return (H, ref Rmsg.Wstat(tag));
	Rattach =>
		return (H+QID, ref Rmsg.Attach(tag, gqid(f, H)));
	Rwalk =>
		nqid := (int f[H+1] << 8) | int f[H];
		if(len f < H+LEN+nqid*QID)
			break;
		o := H+LEN;
		qids := array[nqid] of Sys->Qid;
		for(i:=0; i<nqid; i++){
			qids[i] = gqid(f, o);
			o += QID;
		}
		return (o, ref Rmsg.Walk(tag, qids));
	Ropen =>
		return (H+QID+COUNT, ref Rmsg.Open(tag, gqid(f, H), g32(f, H+QID)));
	Rcreate=>
		return (H+QID+COUNT, ref Rmsg.Create(tag, gqid(f, H), g32(f, H+QID)));
	Rread =>
		count := g32(f, H);
		if(len f < H+COUNT+count)
			break;
		data := f[H+COUNT:H+COUNT+count];
		return (H+COUNT+count, ref Rmsg.Read(tag, data));
	Rwrite =>
		return (H+COUNT, ref Rmsg.Write(tag, g32(f, H)));
	Rstat =>
		n := (int f[H+1] << 8) | int f[H];
		if(len f < H+LEN+n)
			break;
		(ds, d) := unpackdir(f[H+LEN:]);
		if(ds <= 0)
			break;
		if(ds != n){
			sys->print("Ninep->Rmsg.unpack: stat count: %d/%d\n", ds, n);		# temporary
			break;
		}
		return (H+LEN+n, ref Rmsg.Stat(tag, d));
	}
	return (-1, nil);		# illegal
}

rmsgname := array[] of {
tagof Rmsg.Version => "Version",
tagof Rmsg.Auth => "Auth",
tagof Rmsg.Attach => "Attach",
tagof Rmsg.Error => "Error",
tagof Rmsg.Flush => "Flush",
tagof Rmsg.Walk => "Walk",
tagof Rmsg.Create => "Create",
tagof Rmsg.Open => "Open",
tagof Rmsg.Read => "Read",
tagof Rmsg.Write => "Write",
tagof Rmsg.Clunk => "Clunk",
tagof Rmsg.Remove => "Remove",
tagof Rmsg.Stat => "Stat",
tagof Rmsg.Wstat => "Wstat",
};

Rmsg.text(r: self ref Rmsg): string
{
	if(sys == nil)
		sys = load Sys Sys->PATH;
	if(r == nil)
		return "nil";
	s := sys->sprint("Rmsg.%s(%ud", rmsgname[tagof r], r.tag);
	pick m := r {
	* =>
		return s + "ERROR)";
	Readerror =>
		return s + sys->sprint(",\"%s\")", m.error);
	Version =>
		return s + sys->sprint(",%d,\"%s\")", m.msize, m.version);
	Auth =>
		return s+sys->sprint(",%s)", qid2text(m.aqid));
	Error =>
		return s+sys->sprint(",\"%s\")", m.ename);
	Flush or
	Clunk or
	Remove or
	Wstat =>
		return s+")";
	Attach =>
		return s+sys->sprint(",%s)", qid2text(m.qid));
	Walk	 =>
		s += ",array[] of {";
		for(i := 0; i < len m.qids; i++){
			c := "";
			if(i != 0)
				c = ",";
			s += sys->sprint("%s%s", c, qid2text(m.qids[i]));
		}
		return s+"})";
	Create or
	Open =>
		return s+sys->sprint(",%s,%d)", qid2text(m.qid), m.iounit);
	Read =>
		return s+sys->sprint(",array[%d] of byte)", len m.data);
	Write =>
		return s+sys->sprint(",%d)", m.count);
	Stat =>
		return s+sys->sprint(",%s)", dir2text(m.stat));
	}
}

Rmsg.read(fd: ref Sys->FD, msglim: int): ref Rmsg
{
	(msg, err) := readmsg(fd, msglim);
	if(err != nil)
		return ref Rmsg.Readerror(0, err);
	if(msg == nil)
		return nil;
	(nil, m) := Rmsg.unpack(msg);
	if(m == nil)
		return ref Rmsg.Readerror(0, "bad 9P R-message format");
	return m;
}

Rmsg.write(m: self ref Rmsg, fd: ref Sys->FD, msize: int): int
{
	if(msize == 0)
		m = ref Rmsg.Error(m.tag, "Tversion not seen");
	d := m.pack();
	if(msize != 0 && len d > msize){
		m = ref Rmsg.Error(m.tag, "9P reply didn't fit");
		d = m.pack();
	}
	n := len d;
	if(sys->write(fd, d, n) != n)
		return -1;
	return 0;
}

dir2text(d: Sys->Dir): string
{
	return sys->sprint("Dir(\"%s\",\"%s\",\"%s\",%s,8r%uo,%d,%d,%bd,16r%ux,%d)",
		d.name, d.uid, d.gid, qid2text(d.qid), d.mode, d.atime, d.mtime, d.length, d.dtype, d.dev);
}

qid2text(q: Sys->Qid): string
{
	return sys->sprint("Qid(16r%ubx,%d,16r%.2ux)", q.path, q.vers, q.qtype);
}

readmsg(fd: ref Sys->FD, msglim: int): (array of byte, string)
{
	if(msglim <= 0)
		msglim = DEFMSIZE;
	sbuf := array[BIT32SZ] of byte;
	if((n := sys->readn(fd, sbuf, BIT32SZ)) != BIT32SZ){
		if(n == 0)
			return (nil, nil);
		return (nil, sys->sprint("%r"));
	}
	ml := (int sbuf[1] << 8) | int sbuf[0];
	ml |= ((int sbuf[3] << 8) | int sbuf[2]) << 16;
	if(ml <= BIT32SZ)
		return (nil, "invalid 9P message size");
	if(ml > msglim)
		return (nil, "9P message longer than agreed");
	buf := array[ml] of byte;
	buf[0:] = sbuf;
	if((n = sys->readn(fd, buf[BIT32SZ:], ml-BIT32SZ)) != ml-BIT32SZ){
		if(n == 0)
			return (nil, "9P message truncated");
		return (nil, sys->sprint("%r"));
	}
	return (buf, nil);
}

istmsg(f: array of byte): int
{
	if(len f < H)
		return -1;
	return (int f[BIT32SZ] & 1) == 0;
}

compatible(t: ref Tmsg.Version, msize: int, version: string): (int, string)
{
	if(version == nil)
		version = VERSION;
	if(t.msize < msize)
		msize = t.msize;
	v := t.version;
	if(len v < 2 || v[0:2] != "9P")
		return (msize, "unknown");
	for(i:=2; i<len v; i++)
		if((c := v[i]) == '.'){
			v = v[0:i];
			break;
		}else if(!(c >= '0' && c <= '9'))
			return (msize, "unknown");	# fussier than Plan 9
	if(v < VERSION)
		return (msize, "unknown");
	if(v < version)
		version = v;
	return (msize, version);
}