~kris/9p

9hist

50f18502fc15527f7b2bbc8933d9085db8ba8046 — David du Colombier 26 years ago 26d34b1
Plan 9 from Bell Labs 1999-10-01
5 files changed, 857 insertions(+), 22 deletions(-)

M pc/devarch.c
M port/devsdp.c
A port/thwack.c
A port/thwack.h
A port/unthwack.c
M pc/devarch.c => pc/devarch.c +1 -0
@@ 409,6 409,7 @@ static X86type x86intel[] =
	{ 6,	1,	16,	"PentiumPro", },/* determined by trial and error */
	{ 6,	3,	16,	"PentiumII", },
	{ 6,	5,	16,	"PentiumII/Xeon", },
	{ 6,	7,	16,	"PentiumIII/Xeon", },

	{ 3,	-1,	32,	"386", },	/* family defaults */
	{ 4,	-1,	22,	"486", },

M port/devsdp.c => port/devsdp.c +202 -22
@@ 7,6 7,7 @@
#include "../port/error.h"

#include	<libcrypt.h>
#include "../port/thwack.h"

/*
 * sdp - secure datagram protocol


@@ 18,6 19,7 @@ typedef struct OneWay OneWay;
typedef struct Stats Stats;
typedef struct ConnectPkt ConnectPkt;
typedef struct AckPkt AckPkt;
typedef struct Algorithm Algorithm;

enum
{


@@ 39,9 41,11 @@ enum

	Maxconv= 256,		// power of 2
	Nfs= 4,				// number of file systems
	MaxRetries=	4,
	MaxRetries=	8,
	KeepAlive = 60,		// keep alive in seconds
	KeyLength= 32,
	SeqMax = (1<<24),
	SeqWindow = 32,
};

#define TYPE(x) 	((x).path & 0xff)


@@ 115,7 119,8 @@ struct Conv {

	Stats	lstats;
	Stats	rstats;

	
	ulong	lastrecv;	// time last packet was received 
	ulong	timeout;
	int		retries;



@@ 133,6 138,9 @@ struct Conv {
	int	perm;

	uchar	masterkey[KeyLength];
	char *authname;
	char *ciphername;
	char *compname;

	int drop;



@@ 193,6 201,12 @@ struct AckPkt
	uchar	inBadSeq[4];
};

struct Algorithm
{
	char 	*name;
	int		keylen;		// in bytes
	void	(*init)(Conv*, char* name, int keylen);
};

static Dirtab sdpdirtab[]={
	"log",		{Qlog},		0,	0666,


@@ 208,6 222,31 @@ static Dirtab convdirtab[]={
	"rstats",	{Qrstats},	0,	0444,
};

#ifdef XXX
static Algorithm cipheralg[] =
{
	"null",			0,	nullcipherinit,
	"des_56_cbc",	7,	descipherinit,
	"rc4_128",		16,	rc4cipherinit,
	nil,			0,	nil,
};

static Algorithm authalg[] =
{
	"null",			0,	nullauthinit,
	"hmac_sha_96",	16,	shaauthinit,
	"hmac_md5_96",	16,	md5authinit,
	nil,			0,	nil,
};

static Algorithm compalg[] =
{
	"null",			0,	nullcompinit,
	"thwack",		0,	thwackcompinit,
	nil,			0,	nil,
};
#endif

static int m2p[] = {
	[OREAD]		4,
	[OWRITE]	2,


@@ 572,7 611,6 @@ sdpwrite(Chan *ch, void *a, long n, vlong off)
			error(p);
		return n;
	case Qcontrol:
print("writecontrol %ld\n", n);
		writecontrol(sdp->conv[CONV(ch->qid)], a, n, 0);
		return n;
	case Qdata:


@@ 660,6 698,7 @@ sdpclone(Sdp *sdp)
			c = malloc(sizeof(Conv));
			if(c == nil)
				error(Enomem);
			memset(c, 0, sizeof(Conv));
			qlock(c);
			c->sdp = sdp;
			c->id = pp - sdp->conv;


@@ 681,6 720,11 @@ sdpclone(Sdp *sdp)

	c->ref++;
	c->state = CInit;
	c->in.window = ~0;
	c->in.compstate = malloc(sizeof(Unthwack));
	unthwackinit(c->in.compstate);
	c->out.compstate = malloc(sizeof(Thwack));
	thwackinit(c->out.compstate);
	strncpy(c->owner, up->user, sizeof(c->owner));
	c->perm = 0660;
	qunlock(c);


@@ 719,7 763,7 @@ convtimer(Conv *c, ulong sec)
{
	Block *b;

	if(c->timeout == 0 || c->timeout > sec)
	if(c->timeout > sec)
		return;
	qlock(c);
	if(waserror()) {


@@ 740,10 784,27 @@ convtimer(Conv *c, ulong sec)
		if(b != nil) {
			if(convretry(c, 1))
				convoput(c, TControl, copyblock(b, blocklen(b)));
		} else {
			c->timeout = 0;
			break;
		}

		c->timeout = c->lastrecv + KeepAlive;
		if(c->timeout > sec)
			break;
		// keepalive - randomly spaced between KeepAlive and 2*KeepAlive
		if(c->timeout + KeepAlive > sec && nrand(c->lastrecv + 2*KeepAlive - sec) > 0)
			break;
print("sending keep alive: %ld\n", sec - c->lastrecv);
		// can not use writecontrol
		b = allocb(4);
		c->out.controlseq++;
		hnputl(b->wp, c->out.controlseq);
		b->wp += 4;
		c->out.controlpkt = b;
		convretryinit(c);
		if(!waserror()) {
			convoput(c, TControl, copyblock(b, blocklen(b)));
			poperror();
		}
		// keepalive
		break;
	case CLocalClose:
		if(convretry(c, 0))


@@ 751,7 812,7 @@ convtimer(Conv *c, ulong sec)
		break;
	case CRemoteClose:
	case CClosed:
		c->timeout = 0;
		c->timeout = ~0;
		break;
	}
	poperror();


@@ 858,16 919,30 @@ print("CClosed -> ref = %d\n", c->ref);
			free(c->channame);
			c->channame = nil;
		}
		strcpy(c->owner, "network");
		if(c->ciphername) {
			free(c->ciphername);
			c->ciphername = nil;
		}
		if(c->authname) {
			free(c->authname);
			c->authname = nil;
		}
			if(c->compname) {
			free(c->compname);
			c->compname = nil;
		}
	strcpy(c->owner, "network");
		c->perm = 0660;
		c->dialid = 0;
		c->acceptid = 0;
		c->timeout = 0;
		c->timeout = ~0;
		c->retries = 0;
		c->drop = 0;
		memset(c->masterkey, 0, sizeof(c->masterkey));
		onewaycleanup(&c->in);
		onewaycleanup(&c->out);
		memset(&c->lstats, 0, sizeof(Stats));
		memset(&c->rstats, 0, sizeof(Stats));
		break;
	}
	c->state = state;


@@ 979,9 1054,12 @@ convack(Conv *c)
static Block *
conviput(Conv *c, Block *b, int control)
{
	int type;
	ulong seq, cseq;
	int type, n;
	ulong seq, seqwrap, cseq;
	long seqdiff;
	AckPkt *ack;
	ulong mseq, mask;
	Block *bb;

	c->lstats.inPackets++;



@@ 999,12 1077,58 @@ conviput(Conv *c, Block *b, int control)
	seq = (b->rp[1]<<16) + (b->rp[2]<<8) + b->rp[3];
	b->rp += 4;

	USED(seq);
	seqwrap = c->in.seqwrap;
	seqdiff = seq - c->in.seq;
	if(seqdiff < -(SeqMax*3/4)) {
		seqwrap++;
		seqdiff += SeqMax;
	} else if(seqdiff > SeqMax*3/4) {
		seqwrap--;
		seqdiff -= SeqMax;
	}

	if(seqdiff <= 0) {
		if(seqdiff <= -SeqWindow) {
print("old sequence number: %ld (%ld %ld)\n", seq, c->in.seqwrap, seqdiff);
			c->lstats.inBadSeq++;
			freeb(b);
			return nil;
		}

		if(c->in.window & (1<<-seqdiff)) {
print("dup sequence number: %ld (%ld %ld)\n", seq, c->in.seqwrap, seqdiff);
			c->lstats.inDup++;
			freeb(b);
			return nil;
		}

		c->lstats.inReorder++;
	}

	// ok the sequence number looks ok
if(0) print("coniput seq=%ulx\n", seq);
	// auth
	// decrypt

	// ok the packet is good
	if(seqdiff > 0) {
		while(seqdiff > 0 && c->in.window != 0) {
			if((c->in.window & (1<<(SeqWindow-1))) == 0) {
print("missing packet: %ld\n", seq - seqdiff);
				c->lstats.inMissing++;
			}
			c->in.window <<= 1;
			seqdiff--;
		}
		if(seqdiff > 0) {
print("missing packets: %ld-%ld\n", seq - SeqWindow - seqdiff+1, seq-SeqWindow);
			c->lstats.inMissing += seqdiff;
		}
		c->in.seq = seq;
		c->in.seqwrap = seqwrap;
		c->in.window |= 1;
	}
	c->lastrecv = TK2SEC(m->ticks);

	switch(type) {
	case TControl:


@@ 1059,6 1183,7 @@ print("ControlAck expected %ulx got %ulx\n", c->out.controlseq, cseq);
		freeb(b);
		freeb(c->out.controlpkt);
		c->out.controlpkt = nil;
		c->timeout = c->lastrecv + KeepAlive;
		wakeup(&c->out.controlready);
		return nil;
	case TData:


@@ 1068,6 1193,35 @@ print("ControlAck expected %ulx got %ulx\n", c->out.controlseq, cseq);
		if(control)
			break;
		return b;
	case TThwackU:
		c->lstats.inDataPackets++;
		c->lstats.inCompDataBytes += BLEN(b);
		mask = b->rp[0];
		mseq = (b->rp[1]<<16) | (b->rp[2]<<8) | b->rp[3];
		b->rp += 4;
		thwackack(c->out.compstate, mseq, mask);
		c->lstats.inDataBytes += BLEN(b);
		if(control)
			break;
		return b;
	case TThwackC:
		c->lstats.inDataPackets++;
		c->lstats.inCompDataBytes += BLEN(b);
		bb = b;
		b = allocb(ThwMaxBlock);
		n = unthwack(c->in.compstate, b->wp, ThwMaxBlock, bb->rp, BLEN(bb), seq);
		freeb(bb);
		if(n < 0)
			break;
		b->wp += n;
		mask = b->rp[0];
		mseq = (b->rp[1]<<16) | (b->rp[2]<<8) | b->rp[3];
		thwackack(c->out.compstate, mseq, mask);
		b->rp += 4;
		c->lstats.inDataBytes += BLEN(b);
		if(control)
			break;
		return b;
	}
print("droping packet %d n=%ld\n", type, BLEN(b));
	freeb(b);


@@ 1112,7 1266,6 @@ print("conviput2: %s: %d %uld %uld\n", convstatename[c->state], con->op, dialid,
		goto Reset;
	}


	switch(con->op) {
	case ConOpenRequest:
		switch(c->state) {


@@ 1189,7 1342,7 @@ static void
convwriteblock(Conv *c, Block *b)
{
	// simulated errors
	if(c->drop && c->drop > nrand(c->drop))
	if(c->drop && nrand(c->drop) == 0)
		return;

	if(waserror()) {


@@ 1388,12 1541,9 @@ writecontrol(Conv *c, void *p, int n, int wait)
	b->wp += 4+n;
	c->out.controlpkt = b;
	convretryinit(c);
print("send %ld size=%ld\n", c->out.controlseq, BLEN(b));	
	convoput(c, TControl, copyblock(b, blocklen(b)));
	if(wait) {
print("writecontrol wait!\n");
	if(wait)
		writewait(c);
	}
	poperror();
	qunlock(c);
	qunlock(&c->out.controllk);


@@ 1424,7 1574,9 @@ readdata(Conv *c, int n)
static long
writedata(Conv *c, Block *b)
{
	int n;
	int n, nn;
	ulong seq;
	Block *bb;

	qlock(c);
	if(waserror()) {


@@ 1440,8 1592,36 @@ writedata(Conv *c, Block *b)
	n = BLEN(b);
	c->lstats.outDataPackets++;
	c->lstats.outDataBytes += n;
	c->lstats.outCompDataBytes += n;
	convoput(c, TData, b);

	if(0) {
		c->lstats.outCompDataBytes += n;
		convoput(c, TData, b);
		poperror();
		qunlock(c);
		return n;
	}
	b = padblock(b, 4);
	b->rp[0] = (c->in.window>>1) & 0xff;
	b->rp[1] = c->in.seq>>16;
	b->rp[2] = c->in.seq>>8;
	b->rp[3] = c->in.seq;

	// must generate same value as convoput
	seq = (c->out.seq + 1) & (SeqMax-1);

	bb = allocb(BLEN(b));
	nn = thwack(c->out.compstate, bb->wp, b->rp, BLEN(b), seq);
	if(nn < 0) {
		c->lstats.outCompDataBytes += BLEN(b);
		convoput(c, TThwackU, b);
		freeb(bb);
	} else {
		c->lstats.outCompDataBytes += nn;
		bb->wp += nn;
		convoput(c, TThwackC, bb);
		freeb(b);
	}

	poperror();
	qunlock(c);
	return n;

A port/thwack.c => port/thwack.c +325 -0
@@ 0,0 1,325 @@
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"

#include "thwack.h"

typedef struct Huff	Huff;
struct Huff
{
	short	bits;				/* length of the code */
	ulong	encode;				/* the code */
};

static	Huff	lentab[MaxLen] =
{
	{1,	0x0},		/* 0 */
	{2,	0x2},		/* 10 */
	{4,	0xc},		/* 1100 */
	{4,	0xd},		/* 1101 */
	{5,	0x1e},		/* 11110 */
	{6,	0x3e},		/* 111110 */
	{6,	0x3f},		/* 111111 */
	{4,	0xe},		/* 1110 */
};

static	void	bitput(Thwack *tw, int c, int n);
static	int	iomegaput(Thwack *tw, ulong v);

void
thwackinit(Thwack *tw)
{
	int i;

	memset(tw, 0, sizeof *tw);
	for(i = 0; i < EWinBlocks; i++){
		tw->blocks[i].data = tw->data[i];
		tw->blocks[i].edata = tw->blocks[i].data;
		tw->blocks[i].hash = tw->hash[i];
		tw->blocks[i].acked = 0;
	}
}

/*
 * acknowledgement for block seq & nearby preds
 */
void
thwackack(Thwack *tw, ulong seq, ulong mask)
{
	int slot, b;

	slot = tw->slot;
	for(;;){
		for(;;){
			slot--;
			if(slot < 0)
				slot += EWinBlocks;
			if(slot == tw->slot)
				return;
			if(tw->blocks[slot].seq == seq){
				tw->blocks[slot].acked = 1;
				break;
			}
		}

		if(mask == 0)
			break;
		do{
			b = mask & 1;
			seq--;
			mask >>= 1;
		}while(!b);
	}
}

/*
 * find a string in the dictionary
 */
static int
thwmatch(ThwBlock *b, ThwBlock *eblocks, uchar **ss, uchar *esrc, ulong h)
{
	int then, toff, w;
	uchar *s, *t;

	s = *ss;
	if(esrc < s + MinMatch)
		return 0;

	toff = 0;
	for(; b < eblocks; b++){
		then = b->hash[h];
		toff += b->maxoff;
		w = (ushort)(then - b->begin);

		if(w >= b->maxoff)
			continue;

		/*
		 * don't need to check for the end because
		 * 1) s too close check above
		 * 2) entries too close not added to hash tables
		 */
		t = w + b->data;
		if(s[0] != t[0] || s[1] != t[1] || s[2] != t[2])
			continue;
		if(esrc - s > b->edata - t)
			esrc = s + (b->edata - t);

		t += 3;
		for(s += 3; s < esrc; s++){
			if(*s != *t)
				break;
			t++;
		}
		*ss = s;
		return toff - w;
	}
	return 0;
}

/*
 * knuth vol. 3 multiplicative hashing
 * each byte x chosen according to rules
 * 1/4 < x < 3/10, 1/3 x < < 3/7, 4/7 < x < 2/3, 7/10 < x < 3/4
 * with reasonable spread between the bytes & their complements
 *
 * the 3 byte value appears to be as almost good as the 4 byte value,
 * and might be faster on some machines
 */
//#define hashit(c)	(((((ulong)(c) & 0xffffff) * 0x6b43a9b5) >> (32 - HashLog)) & HashMask)
#define hashit(c)	((((ulong)(c) * 0x6b43a9) >> (24 - HashLog)) & HashMask)

/*
 * lz77 compression with single lookup in a hash table for each block
 */
int
thwack(Thwack *tw, uchar *dst, uchar *src, int n, ulong seq)
{
	ThwBlock *eblocks, *b, blocks[CompBlocks];
	uchar *s, *ss, *sss, *esrc, *half;
	ulong cont, cseq, bseq, cmask;
	int now, toff;
	int h, m, slot, bits, totmatched;

	if(n > ThwMaxBlock || n < MinMatch || waserror())
		return -1;

	tw->dst = dst;
	tw->dmax = dst + n;
	tw->nbits = 0;

	/*
	 * add source to the coding window
	 * there is always enough space
	 */
	slot = tw->slot;
	b = &tw->blocks[slot];
	b->seq = seq;
	b->acked = 0;
	now = b->begin + b->maxoff;
	s = b->data;
	memmove(s, src, n);
	b->edata = s + n;
	b->begin = now;
	b->maxoff = n;

	/*
	 * set up the history blocks
	 */
	cseq = seq;
	cmask = 0;
	*blocks = *b;
	b = blocks;
	b->maxoff = 0;
	b++;
	while(b < blocks + CompBlocks){
		slot--;
		if(slot < 0)
			slot += EWinBlocks;
		if(slot == tw->slot)
			break;
		if(!tw->blocks[slot].acked)
			continue;
		bseq = tw->blocks[slot].seq;
		if(cseq == seq){
			if(seq - bseq >= MaxSeqStart)
				break;
			cseq = bseq;
		}else if(cseq - bseq > MaxSeqMask)
			break;
		else
			cmask |= 1 << (cseq - bseq - 1);
		*b = tw->blocks[slot];
		b++;
	}
	eblocks = b;
	bitput(tw, ((seq - cseq) << MaxSeqMask) | cmask, 16);

	cont = (s[0] << 16) | (s[2] << 8) | s[2];

	totmatched = 0;
	esrc = s + n;
	half = s + (n >> 1);
	while(s < esrc){
		h = hashit(cont);

		sss = s;
		toff = thwmatch(blocks, eblocks, &sss, esrc, h);
		ss = sss;

		m = ss - s;
		if(m < MinMatch){
			bitput(tw, 0x100|*s, 9);
			ss = s + 1;
		}else{
			totmatched += m;

			toff--;
			for(bits = OffBase; toff >= (1 << bits); bits++)
				;
			if(bits >= MaxOff+OffBase)
				error("thwack offset");
			bitput(tw, bits - OffBase, 4);
			if(bits != OffBase)
				bits--;
			bitput(tw, toff & ((1 << bits) - 1), bits);

			m -= MinMatch;
			if(m < MaxLen-1){
				bitput(tw, lentab[m].encode, lentab[m].bits);
			}else{
				bitput(tw, lentab[MaxLen-1].encode, lentab[MaxLen-1].bits);
				iomegaput(tw, m - (MaxLen - 2));
			}
		}
		blocks->maxoff += ss - s;

		/*
		 * speed hack
		 * check for compression progress, bail if none achieved
		 */
		if(s < half && ss >= half && totmatched * 10 < n)
			error("thwack likely expanding");

		for(; s != ss; s++){
			if(s + MinMatch <= esrc){
				h = hashit(cont);
				blocks->hash[h] = now;
				if(s + MinMatch < esrc)
					cont = (cont << 8) | s[MinMatch];
			}
			now++;
		}
	}

	if(tw->nbits)
		bitput(tw, 0, 8 - tw->nbits);
	tw->slot++;
	if(tw->slot >= EWinBlocks)
		tw->slot = 0;

	poperror();
	return tw->dst - dst;
}

static void
bitput(Thwack *tw, int c, int n)
{
	tw->bits = (tw->bits << n) | c;
	for(tw->nbits += n; tw->nbits >= 8; tw->nbits -= 8){
		if(tw->dst >= tw->dmax)
			error("thwack expanding");
		*tw->dst++ = tw->bits >> (tw->nbits - 8);
	}
}

/*
 * elias's omega code, modified
 * for at least 3 bit transmission
 */
static Huff omegatab[16] = 
{
	{0,	0},		/* 1 */
	{0,	0},
	{0,	0},
	{0,	0},
	{3,	0x4},		/* 5 */
	{3,	0x5},
	{3,	0x6},
	{3,	0x7},
	{7,	0x48},
	{7,	0x49},		/* 10 */
	{7,	0x4a},
	{7,	0x4b},
	{7,	0x4c},
	{7,	0x4d},
	{7,	0x4e},		/* 15 */
	{7,	0x4f},
};

static int
iomegaput(Thwack *tw, ulong v)
{
	int b, bb;

	v--;
	if(v < 4){
		bitput(tw, v, 3);
		return 3;
	}
	if(v < 16){
		b = omegatab[v].bits + 1;
		bitput(tw, omegatab[v].encode << 1, b);
		return b;
	}

	for(bb = 5; v >= (1 << bb); bb++)
		;
	if(bb >= 16)
		error("thwack omegaput");
	b = bb + omegatab[bb].bits + 1;
	bitput(tw, (omegatab[bb].encode << (bb + 1)) | (v << 1), b);
	return b;
}

A port/thwack.h => port/thwack.h +76 -0
@@ 0,0 1,76 @@
typedef struct Thwack		Thwack;
typedef struct Unthwack		Unthwack;
typedef struct ThwBlock		ThwBlock;
typedef struct UnthwBlock	UnthwBlock;

enum
{
	ThwMaxBlock	= 1600,		/* max size of compressible block */

	MinMatch	= 3,		/* shortest match possible */
	HashLog		= 10,
	HashSize	= 1<<HashLog,
	HashMask	= HashSize - 1,

	MaxLen		= 8,
	MaxOff		= 8,
	OffBase		= 6,

	MinDecode	= 9,		/* minimum bits to decode a match or lit */

	EWinBlocks	= 32,		/* blocks held in encoder window */
	DWinBlocks	= 32,		/* blocks held in decoder window */
	CompBlocks	= 5,		/* max blocks used to encode data */

	MaxSeqMask	= 8,		/* number of bits in coding block mask */
	MaxSeqStart	= 256,		/* max offset of initial coding block */
};

struct ThwBlock
{
	ulong	seq;			/* sequence number for this data */
	uchar	acked;			/* ok to use this block; the decoder has it */
	ushort	begin;			/* time of first byte in hash */
	uchar	*edata;			/* last byte of valid data */
	ushort	maxoff;			/* time of last valid hash entry */
	ushort	*hash;
	uchar	*data;
};

struct Thwack
{
	ulong		nbits;		/* output bit buffer */
	ulong		bits;
	uchar		*dst;		/* output buffer */
	uchar		*dmax;

	ulong		slot;		/* next block to use */
	ThwBlock	blocks[EWinBlocks];
	ushort		hash[EWinBlocks][HashSize];
	uchar		data[EWinBlocks][ThwMaxBlock];
};

struct UnthwBlock
{
	ulong	seq;			/* sequence number for this data */
	ushort	maxoff;			/* valid data in each block */
	uchar	*data;
};

struct Unthwack
{
	ulong		nbits;		/* input bit buffer */
	ulong		bits;
	uchar		*src;		/* input buffer */
	uchar		*smax;

	int		slot;		/* next block to use */
	UnthwBlock	blocks[DWinBlocks];
	uchar		data[DWinBlocks][ThwMaxBlock];
};

void	thwackinit(Thwack*);
void	unthwackinit(Unthwack*);
int	thwack(Thwack*, uchar *dst, uchar *src, int nsrc, ulong seq);
void	thwackack(Thwack*, ulong seq, ulong mask);
int	unthwack(Unthwack*, uchar *dst, int ndst, uchar *src, int nsrc, ulong seq);

A port/unthwack.c => port/unthwack.c +253 -0
@@ 0,0 1,253 @@
#include "u.h"
#include "../port/lib.h"
#include "mem.h"
#include "dat.h"
#include "fns.h"

#include "thwack.h"

typedef struct HuffDec		HuffDec;

struct HuffDec
{
	ulong	maxcode[MaxLen];
	ulong	last[MaxLen];
	ulong	decode[MaxLen];
};

static HuffDec lentab = 
{
/*	0	1	2	3	4	5	6	*/
	{0,	0,	0x2,	0,	0xe,	0x1e,	0x3f},
	{-1,	0+0,	0x2+1,	-1,	0xe+2,	0x1e+5,	0x3f+6},
	{
		0,
		1,
		7, 3, 2,
		4,
		6, 5
	},
};

static ulong	bitget(Unthwack *ut, int nb);
static ulong	iomegaget(Unthwack *ut);

void
unthwackinit(Unthwack *ut)
{
	int i;

	memset(ut, 0, sizeof *ut);
	for(i = 0; i < EWinBlocks; i++)
		ut->blocks[i].data = ut->data[i];
}

/*
 * to speed up, inline bitget
 */
int
unthwack(Unthwack *ut, uchar *dst, int ndst, uchar *src, int nsrc, ulong seq)
{
	UnthwBlock blocks[CompBlocks], *b, *eblocks;
	uchar *s, *es, *d, *dmax;
	ulong cmask, cseq, bseq, utbits, utnbits;
	int off, len, bits, slot, tslot;

	if(nsrc < 4 || nsrc > ThwMaxBlock || waserror())
		return -1;

	ut->src = src + 2;
	ut->smax = src + nsrc;

	/*
	 * find the correct slot for this block,
	 * the oldest block around.  the encoder
	 * doesn't use a history at wraparound,
	 * so don't worry about that case.
	 */
	tslot = ut->slot;
	for(;;){
		slot = tslot - 1;
		if(slot < 0)
			slot += DWinBlocks;
		if(ut->blocks[slot].seq <= seq)
			break;
		ut->blocks[slot] = ut->blocks[slot];
		tslot = slot;
	}
	b = blocks;
	ut->blocks[tslot].seq = seq;
	ut->blocks[tslot].maxoff = 0;
	*b = ut->blocks[tslot];
	d = b->data;
	dmax = d + ndst;

	/*
	 * set up the history blocks
	 */
	cseq = seq - src[0];
	cmask = src[1];
	b++;
	slot = tslot;
	while(cseq != seq && b < blocks + CompBlocks){
		slot--;
		if(slot < 0)
			slot += DWinBlocks;
		if(slot == ut->slot)
			break;
		bseq = ut->blocks[slot].seq;
		if(bseq == cseq){
			*b = ut->blocks[slot];
			b++;
			if(cmask == 0){
				cseq = seq;
				break;
			}
			do{
				bits = cmask & 1;
				cseq--;
				cmask >>= 1;
			}while(!bits);
		}
	}
	eblocks = b;
	if(cseq != seq){
		print("blocks not in decompression window: cseq=%d seq=%d cmask=%ux nb=%d\n", cseq, seq, cmask, eblocks - blocks);
		error("unthwack bad window");
	}

	utnbits = 0;
	utbits = 0;
	while(ut->src < ut->smax || ut->nbits >= MinDecode){
		while(utnbits < 9){
			if(ut->src >= ut->smax)
				error("unthwack eof");
			utbits <<= 8;
			utbits |= *ut->src++;
			utnbits += 8;
		}
		utnbits -= 9;
		off = (utbits >> utnbits) & ((1 << 9) - 1);

		bits = off >> 5;
		if(bits >= MaxOff){
			*d++ = off;
			blocks->maxoff++;
			continue;
		}
		off &= (1 << 5) - 1;
		if(bits){
			bits--;
			off |= 1 << 5;
		}
		bits += OffBase - 5;
		off <<= bits;

		while(utnbits < bits){
			if(ut->src >= ut->smax)
				error("unthwack eof");
			utbits <<= 8;
			utbits |= *ut->src++;
			utnbits += 8;
		}
		utnbits -= bits;
		off |= (utbits >> utnbits) & ((1 << bits) - 1);
		off++;

		len = 0;
		bits = 0;
		do{
			len <<= 1;
			if(utnbits < 1){
				if(ut->src >= ut->smax)
					error("unthwack eof");
				utbits <<= 8;
				utbits |= *ut->src++;
				utnbits += 8;
			}
			utnbits--;
			len |= (utbits >> utnbits) & 1;
			bits++;
		}while(len > lentab.maxcode[bits]);
		len = lentab.decode[lentab.last[bits] - len];

		if(len == MaxLen - 1){
			ut->nbits = utnbits;
			ut->bits = utbits;
			len += iomegaget(ut) - 1;
			utnbits = ut->nbits;
			utbits = ut->bits;
		}
		len += MinMatch;

		b = blocks;
		while(off > b->maxoff){
			off -= b->maxoff;
			b++;
			if(b >= eblocks)
				error("unthwack offset");
		}
		if(d + len > dmax
		|| b != blocks && len > off)
			error("unthwack len");
		s = b->data + b->maxoff - off;
		es = s + len;
		while(s < es)
			*d++ = *s++;
		blocks->maxoff += len;
	}

	len = d - blocks->data;
	memmove(dst, blocks->data, len);
	ut->blocks[tslot].maxoff = len;

	ut->slot++;
	if(ut->slot >= DWinBlocks)
		ut->slot = 0;

	poperror();
	return len;
}

/*
 * elias's omega code, modified
 * for at least 3 bit transmission
 */
static ulong
iomegaget(Unthwack *ut)
{
	ulong v;
	int b;

	v = bitget(ut, 3);
	if((v & 0x4) == 0)
		return v + 1;
	for(;;){
		b = bitget(ut, 1);
		if(b == 0)
			return v + 1;
		if(v > 16)
			break;
		v--;
		v = (b << v) | bitget(ut, v);
	}
	error("unthwack iomegaget");
	return ~0;
}

static ulong
bitget(Unthwack *ut, int nb)
{
	int c;

	while(ut->nbits < nb){
		if(ut->src >= ut->smax)
			error("unthwack eof");
		c = *ut->src++;
		ut->bits <<= 8;
		ut->bits |= c;
		ut->nbits += 8;
	}
	ut->nbits -= nb;
	return (ut->bits >> ut->nbits) & ((1 << nb) - 1);
}