#include "u.h" #include "../port/lib.h" #include "mem.h" #include "dat.h" #include "fns.h" #include "../port/netif.h" #include "../port/error.h" #include #include "../port/thwack.h" /* * sdp - secure datagram protocol */ typedef struct Sdp Sdp; typedef struct Conv Conv; typedef struct OneWay OneWay; typedef struct Stats Stats; typedef struct AckPkt AckPkt; typedef struct Algorithm Algorithm; typedef struct CipherRc4 CipherRc4; enum { Qtopdir= 1, /* top level directory */ Qsdpdir, /* sdp directory */ Qclone, Qlog, Qconvdir, /* directory per conversation */ Qctl, Qdata, /* unreliable packet channel */ Qcontrol, /* reliable control channel */ Qstatus, Qstats, Qrstats, MaxQ, Maxconv= 256, // power of 2 Nfs= 4, // number of file systems MaxRetries= 12, KeepAlive = 300, // keep alive in seconds - should probably be about 60 but is higher to avoid linksys bug SecretLength= 32, // a secret per direction SeqMax = (1<<24), SeqWindow = 32, NCompStats = 8, }; #define TYPE(x) (((ulong)(x).path) & 0xff) #define CONV(x) ((((ulong)(x).path) >> 8)&(Maxconv-1)) #define QID(x, y) (((x)<<8) | (y)) struct Stats { ulong outPackets; ulong outDataPackets; ulong outDataBytes; ulong outCompDataBytes; ulong outCompBytes; ulong outCompStats[NCompStats]; ulong inPackets; ulong inDataPackets; ulong inDataBytes; ulong inCompDataBytes; ulong inMissing; ulong inDup; ulong inReorder; ulong inBadComp; ulong inBadAuth; ulong inBadSeq; ulong inBadOther; }; struct OneWay { Rendez statsready; ulong seqwrap; // number of wraps of the sequence number ulong seq; ulong window; uchar secret[SecretLength]; QLock controllk; Rendez controlready; Block *controlpkt; // control channel ulong controlseq; void *cipherstate; // state cipher int cipherivlen; // initial vector length int cipherblklen; // block length int (*cipher)(OneWay*, uchar *buf, int len); void *authstate; // auth state int authlen; // auth data length in bytes int (*auth)(OneWay*, uchar *buf, int len); void *compstate; int (*comp)(Conv*, int subtype, ulong seq, Block **); }; // conv states enum { CFree, CInit, CDial, CAccept, COpen, CLocalClose, CRemoteClose, CClosed, }; struct Conv { QLock; Sdp *sdp; int id; int ref; // holds conv up int state; int dataopen; // ref count of opens on Qdata int controlopen; // ref count of opens on Qcontrol int reader; // reader proc has been started Stats lstats; Stats rstats; ulong lastrecv; // time last packet was received ulong timeout; int retries; // the following pair uniquely define conversation on this port ulong dialid; ulong acceptid; QLock readlk; // protects readproc Proc *readproc; Chan *chan; // packet channel char *channame; char owner[KNAMELEN]; /* protections */ int perm; Algorithm *auth; Algorithm *cipher; Algorithm *comp; int drop; OneWay in; OneWay out; }; struct Sdp { QLock; Log; int nconv; Conv *conv[Maxconv]; int ackproc; }; enum { TConnect, TControl, TData, TCompData, }; enum { ControlMesg, ControlAck, }; enum { ThwackU, ThwackC, }; enum { ConOpenRequest, ConOpenAck, ConOpenAckAck, ConClose, ConCloseAck, ConReset, }; struct AckPkt { uchar cseq[4]; uchar outPackets[4]; uchar outDataPackets[4]; uchar outDataBytes[4]; uchar outCompDataBytes[4]; uchar outCompStats[4*NCompStats]; uchar inPackets[4]; uchar inDataPackets[4]; uchar inDataBytes[4]; uchar inCompDataBytes[4]; uchar inMissing[4]; uchar inDup[4]; uchar inReorder[4]; uchar inBadComp[4]; uchar inBadAuth[4]; uchar inBadSeq[4]; uchar inBadOther[4]; }; struct Algorithm { char *name; int keylen; // in bytes void (*init)(Conv*); }; enum { RC4forward = 10*1024*1024, // maximum skip forward RC4back = 100*1024, // maximum look back }; struct CipherRc4 { ulong cseq; // current byte sequence number RC4state current; int ovalid; // old is valid ulong lgseq; // last good sequence ulong oseq; // old byte sequence number RC4state old; }; static Dirtab sdpdirtab[]={ "log", {Qlog}, 0, 0666, "clone", {Qclone}, 0, 0666, }; static Dirtab convdirtab[]={ "ctl", {Qctl}, 0, 0666, "data", {Qdata}, 0, 0666, "control", {Qcontrol}, 0, 0666, "status", {Qstatus}, 0, 0444, "stats", {Qstats}, 0, 0444, "rstats", {Qrstats}, 0, 0444, }; static int m2p[] = { [OREAD] 4, [OWRITE] 2, [ORDWR] 6 }; enum { Logcompress= (1<<0), Logauth= (1<<1), Loghmac= (1<<2), }; static Logflag logflags[] = { { "compress", Logcompress, }, { "auth", Logauth, }, { "hmac", Loghmac, }, { nil, 0, }, }; static Dirtab *dirtab[MaxQ]; static Sdp sdptab[Nfs]; static char *convstatename[] = { [CFree] "Free", [CInit] "Init", [CDial] "Dial", [CAccept] "Accept", [COpen] "Open", [CLocalClose] "LocalClose", [CRemoteClose] "RemoteClose", [CClosed] "Closed", }; static int sdpgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp); static Conv *sdpclone(Sdp *sdp); static void sdpackproc(void *a); static void onewaycleanup(OneWay *ow); static int readready(void *a); static int controlread(); static void convsetstate(Conv *c, int state); static Block *readcontrol(Conv *c, int n); static void writecontrol(Conv *c, void *p, int n, int wait); static Block *readdata(Conv *c, int n); static long writedata(Conv *c, Block *b); static void convderef(Conv *c); static Block *conviput(Conv *c, Block *b, int control); static void conviconnect(Conv *c, int op, Block *b); static void convicontrol(Conv *c, int op, Block *b); static Block *convicomp(Conv *c, int op, ulong, Block *b); static void convoput(Conv *c, int type, int subtype, Block *b); static void convoconnect(Conv *c, int op, ulong dialid, ulong acceptid); static void convopenchan(Conv *c, char *path); static void convstats(Conv *c, int local, char *buf, int n); static void convreader(void *a); static void setalg(Conv *c, char *name, Algorithm *tab, Algorithm **); static void setsecret(OneWay *cc, char *secret); static void nullcipherinit(Conv*c); static void descipherinit(Conv*c); static void rc4cipherinit(Conv*c); static void nullauthinit(Conv*c); static void shaauthinit(Conv*c); static void md5authinit(Conv*c); static void nullcompinit(Conv*c); static void thwackcompinit(Conv*c); static Algorithm cipheralg[] = { "null", 0, nullcipherinit, "des_56_cbc", 7, descipherinit, "rc4_128", 16, rc4cipherinit, "rc4_256", 32, rc4cipherinit, nil, 0, nil, }; static Algorithm authalg[] = { "null", 0, nullauthinit, "hmac_sha1_96", 16, shaauthinit, "hmac_md5_96", 16, md5authinit, nil, 0, nil, }; static Algorithm compalg[] = { "null", 0, nullcompinit, "thwack", 0, thwackcompinit, nil, 0, nil, }; static void sdpinit(void) { int i; Dirtab *dt; // setup dirtab with non directory entries for(i=0; iqid)] = dt; } for(i=0; iqid)] = dt; } } static Chan* sdpattach(char* spec) { Chan *c; int dev; char buf[100]; Sdp *sdp; int start; dev = atoi(spec); if(dev<0 || dev >= Nfs) error("bad specification"); c = devattach('E', spec); c->qid = (Qid){QID(0, Qtopdir), 0, QTDIR}; c->dev = dev; sdp = sdptab + dev; qlock(sdp); start = sdp->ackproc == 0; sdp->ackproc = 1; qunlock(sdp); if(start) { snprint(buf, sizeof(buf), "sdpackproc%d", dev); kproc(buf, sdpackproc, sdp); } return c; } static Walkqid* sdpwalk(Chan *c, Chan *nc, char **name, int nname) { return devwalk(c, nc, name, nname, 0, 0, sdpgen); } static int sdpstat(Chan* c, uchar* db, int n) { return devstat(c, db, n, nil, 0, sdpgen); } static Chan* sdpopen(Chan* ch, int omode) { int perm; Sdp *sdp; Conv *c; omode &= 3; perm = m2p[omode]; USED(perm); sdp = sdptab + ch->dev; switch(TYPE(ch->qid)) { default: break; case Qtopdir: case Qsdpdir: case Qconvdir: if(omode != OREAD) error(Eperm); break; case Qlog: logopen(sdp); break; case Qclone: c = sdpclone(sdp); if(c == nil) error(Enodev); ch->qid.path = QID(c->id, Qctl); break; case Qdata: case Qctl: case Qstatus: case Qcontrol: case Qstats: case Qrstats: c = sdp->conv[CONV(ch->qid)]; qlock(c); if(waserror()) { qunlock(c); nexterror(); } if((perm & (c->perm>>6)) != perm) if(strcmp(up->user, c->owner) != 0 || (perm & c->perm) != perm) error(Eperm); c->ref++; if(TYPE(ch->qid) == Qdata) { c->dataopen++; // kill reader if Qdata is opened for the first time if(c->dataopen == 1) if(c->readproc != nil) postnote(c->readproc, 1, "interrupt", 0); } else if(TYPE(ch->qid) == Qcontrol) { c->controlopen++; } qunlock(c); poperror(); break; } ch->mode = openmode(omode); ch->flag |= COPEN; ch->offset = 0; return ch; } static void sdpclose(Chan* ch) { Sdp *sdp = sdptab + ch->dev; Conv *c; if(!(ch->flag & COPEN)) return; switch(TYPE(ch->qid)) { case Qlog: logclose(sdp); break; case Qctl: case Qstatus: case Qstats: case Qrstats: c = sdp->conv[CONV(ch->qid)]; qlock(c); convderef(c); qunlock(c); break; case Qdata: c = sdp->conv[CONV(ch->qid)]; qlock(c); c->dataopen--; convderef(c); if(c->dataopen == 0) if(c->reader == 0) if(c->chan != nil) if(!waserror()) { kproc("convreader", convreader, c); c->reader = 1; c->ref++; poperror(); } qunlock(c); break; case Qcontrol: c = sdp->conv[CONV(ch->qid)]; qlock(c); c->controlopen--; convderef(c); if(c->controlopen == 0 && c->ref != 0) { switch(c->state) { default: convsetstate(c, CClosed); break; case CAccept: case COpen: convsetstate(c, CLocalClose); break; } } qunlock(c); break; } } static long sdpread(Chan *ch, void *a, long n, vlong off) { char buf[256]; char *s; Sdp *sdp = sdptab + ch->dev; Conv *c; Block *b; int rv; USED(off); switch(TYPE(ch->qid)) { default: error(Eperm); case Qtopdir: case Qsdpdir: case Qconvdir: return devdirread(ch, a, n, 0, 0, sdpgen); case Qlog: return logread(sdp, a, off, n); case Qstatus: c = sdp->conv[CONV(ch->qid)]; qlock(c); n = readstr(off, a, n, convstatename[c->state]); qunlock(c); return n; case Qctl: sprint(buf, "%lud", CONV(ch->qid)); return readstr(off, a, n, buf); case Qcontrol: b = readcontrol(sdp->conv[CONV(ch->qid)], n); if(b == nil) return 0; if(BLEN(b) < n) n = BLEN(b); memmove(a, b->rp, n); freeb(b); return n; case Qdata: b = readdata(sdp->conv[CONV(ch->qid)], n); if(b == nil) return 0; if(BLEN(b) < n) n = BLEN(b); memmove(a, b->rp, n); freeb(b); return n; case Qstats: case Qrstats: c = sdp->conv[CONV(ch->qid)]; s = smalloc(1000); convstats(c, TYPE(ch->qid) == Qstats, s, 1000); rv = readstr(off, a, n, s); free(s); return rv; } } static Block* sdpbread(Chan* ch, long n, ulong offset) { Sdp *sdp = sdptab + ch->dev; if(TYPE(ch->qid) != Qdata) return devbread(ch, n, offset); return readdata(sdp->conv[CONV(ch->qid)], n); } static long sdpwrite(Chan *ch, void *a, long n, vlong off) { Sdp *sdp = sdptab + ch->dev; Cmdbuf *cb; char *arg0; char *p; Conv *c; Block *b; USED(off); switch(TYPE(ch->qid)) { default: error(Eperm); case Qctl: c = sdp->conv[CONV(ch->qid)]; cb = parsecmd(a, n); qlock(c); if(waserror()) { qunlock(c); free(cb); nexterror(); } if(cb->nf == 0) error("short write"); arg0 = cb->f[0]; if(strcmp(arg0, "accept") == 0) { if(cb->nf != 2) error("usage: accept file"); convopenchan(c, cb->f[1]); } else if(strcmp(arg0, "dial") == 0) { if(cb->nf != 2) error("usage: dial file"); convopenchan(c, cb->f[1]); convsetstate(c, CDial); } else if(strcmp(arg0, "drop") == 0) { if(cb->nf != 2) error("usage: drop permil"); c->drop = atoi(cb->f[1]); } else if(strcmp(arg0, "cipher") == 0) { if(cb->nf != 2) error("usage: cipher alg"); setalg(c, cb->f[1], cipheralg, &c->cipher); } else if(strcmp(arg0, "auth") == 0) { if(cb->nf != 2) error("usage: auth alg"); setalg(c, cb->f[1], authalg, &c->auth); } else if(strcmp(arg0, "comp") == 0) { if(cb->nf != 2) error("usage: comp alg"); setalg(c, cb->f[1], compalg, &c->comp); } else if(strcmp(arg0, "insecret") == 0) { if(cb->nf != 2) error("usage: insecret secret"); setsecret(&c->in, cb->f[1]); if(c->cipher) c->cipher->init(c); if(c->auth) c->auth->init(c); } else if(strcmp(arg0, "outsecret") == 0) { if(cb->nf != 2) error("usage: outsecret secret"); setsecret(&c->out, cb->f[1]); if(c->cipher) c->cipher->init(c); if(c->auth) c->auth->init(c); } else error("unknown control request"); poperror(); qunlock(c); free(cb); return n; case Qlog: cb = parsecmd(a, n); p = logctl(sdp, cb->nf, cb->f, logflags); free(cb); if(p != nil) error(p); return n; case Qcontrol: writecontrol(sdp->conv[CONV(ch->qid)], a, n, 0); return n; case Qdata: b = allocb(n); memmove(b->wp, a, n); b->wp += n; return writedata(sdp->conv[CONV(ch->qid)], b); } } long sdpbwrite(Chan *ch, Block *bp, ulong offset) { Sdp *sdp = sdptab + ch->dev; if(TYPE(ch->qid) != Qdata) return devbwrite(ch, bp, offset); return writedata(sdp->conv[CONV(ch->qid)], bp); } static int sdpgen(Chan *c, char*, Dirtab*, int, int s, Dir *dp) { Sdp *sdp = sdptab + c->dev; int type = TYPE(c->qid); Dirtab *dt; Qid qid; if(s == DEVDOTDOT){ switch(TYPE(c->qid)){ case Qtopdir: case Qsdpdir: snprint(up->genbuf, sizeof(up->genbuf), "#E%ld", c->dev); mkqid(&qid, Qtopdir, 0, QTDIR); devdir(c, qid, up->genbuf, 0, eve, 0555, dp); break; case Qconvdir: snprint(up->genbuf, sizeof(up->genbuf), "%d", s); mkqid(&qid, Qsdpdir, 0, QTDIR); devdir(c, qid, up->genbuf, 0, eve, 0555, dp); break; default: panic("sdpwalk %llux", c->qid.path); } return 1; } switch(type) { default: // non directory entries end up here if(c->qid.type & QTDIR) panic("sdpgen: unexpected directory"); if(s != 0) return -1; dt = dirtab[TYPE(c->qid)]; if(dt == nil) panic("sdpgen: unknown type: %lud", TYPE(c->qid)); devdir(c, c->qid, dt->name, dt->length, eve, dt->perm, dp); return 1; case Qtopdir: if(s != 0) return -1; mkqid(&qid, QID(0, Qsdpdir), 0, QTDIR); devdir(c, qid, "sdp", 0, eve, 0555, dp); return 1; case Qsdpdir: if(sqid, dt->name, dt->length, eve, dt->perm, dp); return 1; } s -= nelem(sdpdirtab); if(s >= sdp->nconv) return -1; mkqid(&qid, QID(s, Qconvdir), 0, QTDIR); snprint(up->genbuf, sizeof(up->genbuf), "%d", s); devdir(c, qid, up->genbuf, 0, eve, 0555, dp); return 1; case Qconvdir: if(s>=nelem(convdirtab)) return -1; dt = convdirtab+s; mkqid(&qid, QID(CONV(c->qid),TYPE(dt->qid)), 0, QTFILE); devdir(c, qid, dt->name, dt->length, eve, dt->perm, dp); return 1; } } static Conv* sdpclone(Sdp *sdp) { Conv *c, **pp, **ep; c = nil; ep = sdp->conv + nelem(sdp->conv); qlock(sdp); if(waserror()) { qunlock(sdp); nexterror(); } for(pp = sdp->conv; pp < ep; pp++) { c = *pp; if(c == nil){ c = malloc(sizeof(Conv)); if(c == nil) error(Enomem); memset(c, 0, sizeof(Conv)); qlock(c); c->sdp = sdp; c->id = pp - sdp->conv; *pp = c; sdp->nconv++; break; } if(c->ref == 0 && canqlock(c)){ if(c->ref == 0) break; qunlock(c); } } poperror(); qunlock(sdp); if(pp >= ep) return nil; assert(c->state == CFree); // set ref to 2 - 1 ref for open - 1 ref for channel state c->ref = 2; c->state = CInit; c->in.window = ~0; strncpy(c->owner, up->user, sizeof(c->owner)); c->perm = 0660; qunlock(c); return c; } // assume c is locked static void convretryinit(Conv *c) { c->retries = 0; // +2 to avoid rounding effects. c->timeout = TK2SEC(m->ticks) + 2; } // assume c is locked static int convretry(Conv *c, int reset) { c->retries++; if(c->retries > MaxRetries) { if(reset) convoconnect(c, ConReset, c->dialid, c->acceptid); convsetstate(c, CClosed); return 0; } c->timeout = TK2SEC(m->ticks) + (c->retries+1); return 1; } // assumes c is locked static void convtimer(Conv *c, ulong sec) { Block *b; if(c->timeout > sec) return; switch(c->state) { case CInit: break; case CDial: if(convretry(c, 1)) convoconnect(c, ConOpenRequest, c->dialid, 0); break; case CAccept: if(convretry(c, 1)) convoconnect(c, ConOpenAck, c->dialid, c->acceptid); break; case COpen: b = c->out.controlpkt; if(b != nil) { if(convretry(c, 1)) convoput(c, TControl, ControlMesg, copyblock(b, blocklen(b))); 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; // 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, ControlMesg, copyblock(b, blocklen(b))); poperror(); } break; case CLocalClose: if(convretry(c, 0)) convoconnect(c, ConClose, c->dialid, c->acceptid); break; case CRemoteClose: case CClosed: break; } } static void sdpackproc(void *a) { Sdp *sdp = a; ulong sec; int i; Conv *c; for(;;) { tsleep(&up->sleep, return0, 0, 1000); sec = TK2SEC(m->ticks); qlock(sdp); for(i=0; inconv; i++) { c = sdp->conv[i]; if(c->ref == 0) continue; qunlock(sdp); qlock(c); if(c->ref > 0 && !waserror()) { convtimer(c, sec); poperror(); } qunlock(c); qlock(sdp); } qunlock(sdp); } } Dev sdpdevtab = { 'E', "sdp", devreset, sdpinit, devshutdown, sdpattach, sdpwalk, sdpstat, sdpopen, devcreate, sdpclose, sdpread, devbread, sdpwrite, devbwrite, devremove, devwstat, }; // assume hold lock on c static void convsetstate(Conv *c, int state) { if(0)print("convsetstate %d: %s -> %s\n", c->id, convstatename[c->state], convstatename[state]); switch(state) { default: panic("setstate: bad state: %d", state); case CDial: assert(c->state == CInit); c->dialid = (rand()<<16) + rand(); convretryinit(c); convoconnect(c, ConOpenRequest, c->dialid, 0); break; case CAccept: assert(c->state == CInit); c->acceptid = (rand()<<16) + rand(); convretryinit(c); convoconnect(c, ConOpenAck, c->dialid, c->acceptid); break; case COpen: assert(c->state == CDial || c->state == CAccept); c->lastrecv = TK2SEC(m->ticks); if(c->state == CDial) { convretryinit(c); convoconnect(c, ConOpenAckAck, c->dialid, c->acceptid); hnputl(c->in.secret, c->acceptid); hnputl(c->in.secret+4, c->dialid); hnputl(c->out.secret, c->dialid); hnputl(c->out.secret+4, c->acceptid); } else { hnputl(c->in.secret, c->dialid); hnputl(c->in.secret+4, c->acceptid); hnputl(c->out.secret, c->acceptid); hnputl(c->out.secret+4, c->dialid); } setalg(c, "hmac_md5_96", authalg, &c->auth); break; case CLocalClose: assert(c->state == CAccept || c->state == COpen); convretryinit(c); convoconnect(c, ConClose, c->dialid, c->acceptid); break; case CRemoteClose: wakeup(&c->in.controlready); wakeup(&c->out.controlready); break; case CClosed: wakeup(&c->in.controlready); wakeup(&c->out.controlready); if(c->readproc) postnote(c->readproc, 1, "interrupt", 0); if(c->state != CClosed) convderef(c); break; } c->state = state; } //assumes c is locked static void convderef(Conv *c) { c->ref--; if(c->ref > 0) { return; } assert(c->ref == 0); assert(c->dataopen == 0); assert(c->controlopen == 0); if(0)print("convderef: %d: ref == 0!\n", c->id); c->state = CFree; if(c->chan) { cclose(c->chan); c->chan = nil; } if(c->channame) { free(c->channame); c->channame = nil; } c->cipher = nil; c->auth = nil; c->comp = nil; strcpy(c->owner, "network"); c->perm = 0660; c->dialid = 0; c->acceptid = 0; c->timeout = 0; c->retries = 0; c->drop = 0; onewaycleanup(&c->in); onewaycleanup(&c->out); memset(&c->lstats, 0, sizeof(Stats)); memset(&c->rstats, 0, sizeof(Stats)); } static void onewaycleanup(OneWay *ow) { if(ow->controlpkt) freeb(ow->controlpkt); if(ow->authstate) free(ow->authstate); if(ow->cipherstate) free(ow->cipherstate); if(ow->compstate) free(ow->compstate); memset(ow, 0, sizeof(OneWay)); } // assumes conv is locked static void convopenchan(Conv *c, char *path) { if(c->state != CInit || c->chan != nil) error("already connected"); c->chan = namec(path, Aopen, ORDWR, 0); c->channame = smalloc(strlen(path)+1); strcpy(c->channame, path); if(waserror()) { cclose(c->chan); c->chan = nil; free(c->channame); c->channame = nil; nexterror(); } kproc("convreader", convreader, c); assert(c->reader == 0 && c->ref > 0); // after kproc in case it fails c->reader = 1; c->ref++; poperror(); } static void convstats(Conv *c, int local, char *buf, int n) { Stats *stats; char *p, *ep; int i; if(local) { stats = &c->lstats; } else { if(!waserror()) { writecontrol(c, 0, 0, 1); poperror(); } stats = &c->rstats; } qlock(c); p = buf; ep = buf + n; p += snprint(p, ep-p, "outPackets: %lud\n", stats->outPackets); p += snprint(p, ep-p, "outDataPackets: %lud\n", stats->outDataPackets); p += snprint(p, ep-p, "outDataBytes: %lud\n", stats->outDataBytes); p += snprint(p, ep-p, "outCompDataBytes: %lud\n", stats->outCompDataBytes); for(i=0; ioutCompStats[i] == 0) continue; p += snprint(p, ep-p, "outCompStats[%d]: %lud\n", i, stats->outCompStats[i]); } p += snprint(p, ep-p, "inPackets: %lud\n", stats->inPackets); p += snprint(p, ep-p, "inDataPackets: %lud\n", stats->inDataPackets); p += snprint(p, ep-p, "inDataBytes: %lud\n", stats->inDataBytes); p += snprint(p, ep-p, "inCompDataBytes: %lud\n", stats->inCompDataBytes); p += snprint(p, ep-p, "inMissing: %lud\n", stats->inMissing); p += snprint(p, ep-p, "inDup: %lud\n", stats->inDup); p += snprint(p, ep-p, "inReorder: %lud\n", stats->inReorder); p += snprint(p, ep-p, "inBadComp: %lud\n", stats->inBadComp); p += snprint(p, ep-p, "inBadAuth: %lud\n", stats->inBadAuth); p += snprint(p, ep-p, "inBadSeq: %lud\n", stats->inBadSeq); p += snprint(p, ep-p, "inBadOther: %lud\n", stats->inBadOther); USED(p); qunlock(c); } // c is locked static void convack(Conv *c) { Block *b; AckPkt *ack; Stats *s; int i; b = allocb(sizeof(AckPkt)); ack = (AckPkt*)b->wp; b->wp += sizeof(AckPkt); s = &c->lstats; hnputl(ack->cseq, c->in.controlseq); hnputl(ack->outPackets, s->outPackets); hnputl(ack->outDataPackets, s->outDataPackets); hnputl(ack->outDataBytes, s->outDataBytes); hnputl(ack->outCompDataBytes, s->outCompDataBytes); for(i=0; ioutCompStats+i*4, s->outCompStats[i]); hnputl(ack->inPackets, s->inPackets); hnputl(ack->inDataPackets, s->inDataPackets); hnputl(ack->inDataBytes, s->inDataBytes); hnputl(ack->inCompDataBytes, s->inCompDataBytes); hnputl(ack->inMissing, s->inMissing); hnputl(ack->inDup, s->inDup); hnputl(ack->inReorder, s->inReorder); hnputl(ack->inBadComp, s->inBadComp); hnputl(ack->inBadAuth, s->inBadAuth); hnputl(ack->inBadSeq, s->inBadSeq); hnputl(ack->inBadOther, s->inBadOther); convoput(c, TControl, ControlAck, b); } // assume we hold lock for c static Block * conviput(Conv *c, Block *b, int control) { int type, subtype; ulong seq, seqwrap; long seqdiff; int pad; c->lstats.inPackets++; if(BLEN(b) < 4) { c->lstats.inBadOther++; freeb(b); return nil; } type = b->rp[0] >> 4; subtype = b->rp[0] & 0xf; b->rp += 1; if(type == TConnect) { conviconnect(c, subtype, b); return nil; } switch(c->state) { case CInit: case CDial: c->lstats.inBadOther++; convoconnect(c, ConReset, c->dialid, c->acceptid); convsetstate(c, CClosed); break; case CAccept: case CRemoteClose: case CLocalClose: c->lstats.inBadOther++; freeb(b); return nil; } seq = (b->rp[0]<<16) + (b->rp[1]<<8) + b->rp[2]; b->rp += 3; 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) { if(0)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)) { if(0)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); if(c->in.auth != 0) { if(!(*c->in.auth)(&c->in, b->rp-4, BLEN(b)+4)) { if(0)print("bad auth %ld\n", BLEN(b)+4); c->lstats.inBadAuth++; freeb(b); return nil; } b->wp -= c->in.authlen; } if(c->in.cipher != 0) { if(!(*c->in.cipher)(&c->in, b->rp, BLEN(b))) { if(0)print("bad cipher\n"); c->lstats.inBadOther++; freeb(b); return nil; } b->rp += c->in.cipherivlen; if(c->in.cipherblklen > 1) { pad = b->wp[-1]; if(pad > BLEN(b)) { if(0)print("pad too big\n"); c->lstats.inBadOther++; freeb(b); return nil; } b->wp -= pad; } } // ok the packet is good if(seqdiff > 0) { while(seqdiff > 0 && c->in.window != 0) { if((c->in.window & (1<<(SeqWindow-1))) == 0) { c->lstats.inMissing++; } c->in.window <<= 1; seqdiff--; } if(seqdiff > 0) { c->lstats.inMissing += seqdiff; seqdiff = 0; } c->in.seq = seq; c->in.seqwrap = seqwrap; } c->in.window |= 1<<-seqdiff; c->lastrecv = TK2SEC(m->ticks); switch(type) { case TControl: convicontrol(c, subtype, b); return nil; case TData: c->lstats.inDataPackets++; c->lstats.inDataBytes += BLEN(b); if(control) break; return b; case TCompData: c->lstats.inDataPackets++; c->lstats.inCompDataBytes += BLEN(b); b = convicomp(c, subtype, seq, b); if(b == nil) { c->lstats.inBadComp++; return nil; } c->lstats.inDataBytes += BLEN(b); if(control) break; return b; } if(0)print("dropping packet id=%d: type=%d n=%ld control=%d\n", c->id, type, BLEN(b), control); c->lstats.inBadOther++; freeb(b); return nil; } // assume hold conv lock static void conviconnect(Conv *c, int subtype, Block *b) { ulong dialid; ulong acceptid; if(BLEN(b) != 8) { freeb(b); return; } dialid = nhgetl(b->rp); acceptid = nhgetl(b->rp + 4); freeb(b); if(0)print("conviconnect: %s: %d %uld %uld\n", convstatename[c->state], subtype, dialid, acceptid); if(subtype == ConReset) { convsetstate(c, CClosed); return; } switch(c->state) { default: panic("unknown state: %d", c->state); case CInit: break; case CDial: if(dialid != c->dialid) goto Reset; break; case CAccept: case COpen: case CLocalClose: case CRemoteClose: if(dialid != c->dialid || subtype != ConOpenRequest && acceptid != c->acceptid) goto Reset; break; case CClosed: goto Reset; } switch(subtype) { case ConOpenRequest: switch(c->state) { case CInit: c->dialid = dialid; convsetstate(c, CAccept); return; case CAccept: case COpen: // duplicate ConOpenRequest that we ignore return; } break; case ConOpenAck: switch(c->state) { case CDial: c->acceptid = acceptid; convsetstate(c, COpen); return; case COpen: // duplicate that we have to ack convoconnect(c, ConOpenAckAck, acceptid, dialid); return; } break; case ConOpenAckAck: switch(c->state) { case CAccept: convsetstate(c, COpen); return; case COpen: case CLocalClose: case CRemoteClose: // duplicate that we ignore return; } break; case ConClose: switch(c->state) { case COpen: convoconnect(c, ConCloseAck, dialid, acceptid); convsetstate(c, CRemoteClose); return; case CRemoteClose: // duplicate ConClose convoconnect(c, ConCloseAck, dialid, acceptid); return; } break; case ConCloseAck: switch(c->state) { case CLocalClose: convsetstate(c, CClosed); return; } break; } Reset: // invalid connection message - reset to sender if(1)print("invalid conviconnect - sending reset\n"); convoconnect(c, ConReset, dialid, acceptid); convsetstate(c, CClosed); } static void convicontrol(Conv *c, int subtype, Block *b) { ulong cseq; AckPkt *ack; int i; if(BLEN(b) < 4) return; cseq = nhgetl(b->rp); switch(subtype){ case ControlMesg: if(cseq == c->in.controlseq) { if(0)print("duplicate control packet: %ulx\n", cseq); // duplicate control packet freeb(b); if(c->in.controlpkt == nil) convack(c); return; } if(cseq != c->in.controlseq+1) return; c->in.controlseq = cseq; b->rp += 4; if(BLEN(b) == 0) { // just a ping freeb(b); convack(c); } else { c->in.controlpkt = b; if(0) print("recv %ld size=%ld\n", cseq, BLEN(b)); wakeup(&c->in.controlready); } return; case ControlAck: if(cseq != c->out.controlseq) return; if(BLEN(b) < sizeof(AckPkt)) return; ack = (AckPkt*)(b->rp); c->rstats.outPackets = nhgetl(ack->outPackets); c->rstats.outDataPackets = nhgetl(ack->outDataPackets); c->rstats.outDataBytes = nhgetl(ack->outDataBytes); c->rstats.outCompDataBytes = nhgetl(ack->outCompDataBytes); for(i=0; irstats.outCompStats[i] = nhgetl(ack->outCompStats + 4*i); c->rstats.inPackets = nhgetl(ack->inPackets); c->rstats.inDataPackets = nhgetl(ack->inDataPackets); c->rstats.inDataBytes = nhgetl(ack->inDataBytes); c->rstats.inCompDataBytes = nhgetl(ack->inCompDataBytes); c->rstats.inMissing = nhgetl(ack->inMissing); c->rstats.inDup = nhgetl(ack->inDup); c->rstats.inReorder = nhgetl(ack->inReorder); c->rstats.inBadComp = nhgetl(ack->inBadComp); c->rstats.inBadAuth = nhgetl(ack->inBadAuth); c->rstats.inBadSeq = nhgetl(ack->inBadSeq); c->rstats.inBadOther = nhgetl(ack->inBadOther); freeb(b); freeb(c->out.controlpkt); c->out.controlpkt = nil; c->timeout = c->lastrecv + KeepAlive; wakeup(&c->out.controlready); return; } } static Block* convicomp(Conv *c, int subtype, ulong seq, Block *b) { if(c->in.comp == nil) { freeb(b); return nil; } if(!(*c->in.comp)(c, subtype, seq, &b)) return nil; return b; } // c is locked static void convwriteblock(Conv *c, Block *b) { // simulated errors if(c->drop && nrand(c->drop) == 0) return; if(waserror()) { convsetstate(c, CClosed); nexterror(); } devtab[c->chan->type]->bwrite(c->chan, b, 0); poperror(); } // assume hold conv lock static void convoput(Conv *c, int type, int subtype, Block *b) { int pad; c->lstats.outPackets++; /* Make room for sdp trailer */ if(c->out.cipherblklen > 1) pad = c->out.cipherblklen - (BLEN(b) + c->out.cipherivlen) % c->out.cipherblklen; else pad = 0; b = padblock(b, -(pad+c->out.authlen)); if(pad) { memset(b->wp, 0, pad-1); b->wp[pad-1] = pad; b->wp += pad; } /* Make space to fit sdp header */ b = padblock(b, 4 + c->out.cipherivlen); b->rp[0] = (type << 4) | subtype; c->out.seq++; if(c->out.seq == (1<<24)) { c->out.seq = 0; c->out.seqwrap++; } b->rp[1] = c->out.seq>>16; b->rp[2] = c->out.seq>>8; b->rp[3] = c->out.seq; if(c->out.cipher) (*c->out.cipher)(&c->out, b->rp+4, BLEN(b)-4); // auth if(c->out.auth) { b->wp += c->out.authlen; (*c->out.auth)(&c->out, b->rp, BLEN(b)); } convwriteblock(c, b); } // assume hold conv lock static void convoconnect(Conv *c, int op, ulong dialid, ulong acceptid) { Block *b; c->lstats.outPackets++; assert(c->chan != nil); b = allocb(9); b->wp[0] = (TConnect << 4) | op; hnputl(b->wp+1, dialid); hnputl(b->wp+5, acceptid); b->wp += 9; if(!waserror()) { convwriteblock(c, b); poperror(); } } static Block * convreadblock(Conv *c, int n) { Block *b; Chan *ch; qlock(&c->readlk); if(waserror()) { c->readproc = nil; qunlock(&c->readlk); nexterror(); } qlock(c); if(c->state == CClosed) { qunlock(c); error("closed"); } c->readproc = up; ch = c->chan; assert(c->ref > 0); qunlock(c); b = devtab[ch->type]->bread(ch, n, 0); c->readproc = nil; poperror(); qunlock(&c->readlk); return b; } static int readready(void *a) { Conv *c = a; return c->in.controlpkt != nil || (c->state == CClosed) || (c->state == CRemoteClose); } static Block * readcontrol(Conv *c, int n) { Block *b; USED(n); qlock(&c->in.controllk); if(waserror()) { qunlock(&c->in.controllk); nexterror(); } qlock(c); // this lock is not held during the sleep below for(;;) { if(c->chan == nil || c->state == CClosed) { qunlock(c); if(0)print("readcontrol: return error - state = %s\n", convstatename[c->state]); error("conversation closed"); } if(c->in.controlpkt != nil) break; if(c->state == CRemoteClose) { qunlock(c); if(0)print("readcontrol: return nil - state = %s\n", convstatename[c->state]); poperror(); return nil; } qunlock(c); sleep(&c->in.controlready, readready, c); qlock(c); } convack(c); b = c->in.controlpkt; c->in.controlpkt = nil; qunlock(c); poperror(); qunlock(&c->in.controllk); return b; } static int writeready(void *a) { Conv *c = a; return c->out.controlpkt == nil || (c->state == CClosed) || (c->state == CRemoteClose); } // c is locked static void writewait(Conv *c) { for(;;) { if(c->state == CFree || c->state == CInit || c->state == CClosed || c->state == CRemoteClose) error("conversation closed"); if(c->state == COpen && c->out.controlpkt == nil) break; qunlock(c); if(waserror()) { qlock(c); nexterror(); } sleep(&c->out.controlready, writeready, c); poperror(); qlock(c); } } static void writecontrol(Conv *c, void *p, int n, int wait) { Block *b; qlock(&c->out.controllk); qlock(c); if(waserror()) { qunlock(c); qunlock(&c->out.controllk); nexterror(); } writewait(c); b = allocb(4+n); c->out.controlseq++; hnputl(b->wp, c->out.controlseq); memmove(b->wp+4, p, n); b->wp += 4+n; c->out.controlpkt = b; convretryinit(c); convoput(c, TControl, ControlMesg, copyblock(b, blocklen(b))); if(wait) writewait(c); poperror(); qunlock(c); qunlock(&c->out.controllk); } static Block * readdata(Conv *c, int n) { Block *b; int nn; for(;;) { // some slack for tunneling overhead nn = n + 100; // make sure size is big enough for control messages if(nn < 1000) nn = 1000; b = convreadblock(c, nn); if(b == nil) return nil; qlock(c); if(waserror()) { qunlock(c); return nil; } b = conviput(c, b, 0); poperror(); qunlock(c); if(b != nil) { if(BLEN(b) > n) b->wp = b->rp + n; return b; } } } static long writedata(Conv *c, Block *b) { int n; ulong seq; int subtype; qlock(c); if(waserror()) { qunlock(c); nexterror(); } if(c->state != COpen) { freeb(b); error("conversation not open"); } n = BLEN(b); c->lstats.outDataPackets++; c->lstats.outDataBytes += n; if(c->out.comp != nil) { // must generate same value as convoput seq = (c->out.seq + 1) & (SeqMax-1); subtype = (*c->out.comp)(c, 0, seq, &b); c->lstats.outCompDataBytes += BLEN(b); convoput(c, TCompData, subtype, b); } else convoput(c, TData, 0, b); poperror(); qunlock(c); return n; } static void convreader(void *a) { Conv *c = a; Block *b; qlock(c); assert(c->reader == 1); while(c->dataopen == 0 && c->state != CClosed) { qunlock(c); b = nil; if(!waserror()) { b = convreadblock(c, 2000); poperror(); } qlock(c); if(b == nil) { if(strcmp(up->errstr, Eintr) != 0) { convsetstate(c, CClosed); break; } } else if(!waserror()) { conviput(c, b, 1); poperror(); } } c->reader = 0; convderef(c); qunlock(c); pexit("hangup", 1); } /* ciphers, authenticators, and compressors */ static void setalg(Conv *c, char *name, Algorithm *alg, Algorithm **p) { for(; alg->name; alg++) if(strcmp(name, alg->name) == 0) break; if(alg->name == nil) error("unknown algorithm"); *p = alg; alg->init(c); } static void setsecret(OneWay *ow, char *secret) { char *p; int i, c; i = 0; memset(ow->secret, 0, sizeof(ow->secret)); for(p=secret; *p; p++) { if(i >= sizeof(ow->secret)*2) break; c = *p; if(c >= '0' && c <= '9') c -= '0'; else if(c >= 'a' && c <= 'f') c -= 'a'-10; else if(c >= 'A' && c <= 'F') c -= 'A'-10; else error("bad character in secret"); if((i&1) == 0) c <<= 4; ow->secret[i>>1] |= c; i++; } } static void setkey(uchar *key, int n, OneWay *ow, char *prefix) { uchar ibuf[SHA1dlen], obuf[MD5dlen], salt[10]; int i, round = 0; while(n > 0){ for(i=0; isecret, sizeof(ow->secret), nil, nil)); i = (n sizeof salt) panic("setkey: you ask too much"); } } static void cipherfree(Conv *c) { if(c->in.cipherstate) { free(c->in.cipherstate); c->in.cipherstate = nil; } if(c->out.cipherstate) { free(c->out.cipherstate); c->out.cipherstate = nil; } c->in.cipher = nil; c->in.cipherblklen = 0; c->out.cipherblklen = 0; c->in.cipherivlen = 0; c->out.cipherivlen = 0; } static void authfree(Conv *c) { if(c->in.authstate) { free(c->in.authstate); c->in.authstate = nil; } if(c->out.authstate) { free(c->out.authstate); c->out.authstate = nil; } c->in.auth = nil; c->in.authlen = 0; c->out.authlen = 0; } static void compfree(Conv *c) { if(c->in.compstate) { free(c->in.compstate); c->in.compstate = nil; } if(c->out.compstate) { free(c->out.compstate); c->out.compstate = nil; } c->in.comp = nil; } static void nullcipherinit(Conv *c) { cipherfree(c); } static int desencrypt(OneWay *ow, uchar *p, int n) { uchar *pp, *ip, *eip, *ep; DESstate *ds = ow->cipherstate; if(n < 8 || (n & 0x7 != 0)) return 0; ep = p + n; memmove(p, ds->ivec, 8); for(p += 8; p < ep; p += 8){ pp = p; ip = ds->ivec; for(eip = ip+8; ip < eip; ) *pp++ ^= *ip++; block_cipher(ds->expanded, p, 0); memmove(ds->ivec, p, 8); } return 1; } static int desdecrypt(OneWay *ow, uchar *p, int n) { uchar tmp[8]; uchar *tp, *ip, *eip, *ep; DESstate *ds = ow->cipherstate; if(n < 8 || (n & 0x7 != 0)) return 0; ep = p + n; memmove(ds->ivec, p, 8); p += 8; while(p < ep){ memmove(tmp, p, 8); block_cipher(ds->expanded, p, 1); tp = tmp; ip = ds->ivec; for(eip = ip+8; ip < eip; ){ *p++ ^= *ip; *ip++ = *tp++; } } return 1; } static void descipherinit(Conv *c) { uchar key[8]; uchar ivec[8]; int i; int n = c->cipher->keylen; cipherfree(c); if(n > sizeof(key)) n = sizeof(key); /* in */ memset(key, 0, sizeof(key)); setkey(key, n, &c->in, "cipher"); memset(ivec, 0, sizeof(ivec)); c->in.cipherblklen = 8; c->in.cipherivlen = 8; c->in.cipher = desdecrypt; c->in.cipherstate = smalloc(sizeof(DESstate)); setupDESstate(c->in.cipherstate, key, ivec); /* out */ memset(key, 0, sizeof(key)); setkey(key, n, &c->out, "cipher"); for(i=0; i<8; i++) ivec[i] = nrand(256); c->out.cipherblklen = 8; c->out.cipherivlen = 8; c->out.cipher = desencrypt; c->out.cipherstate = smalloc(sizeof(DESstate)); setupDESstate(c->out.cipherstate, key, ivec); } static int rc4encrypt(OneWay *ow, uchar *p, int n) { CipherRc4 *cr = ow->cipherstate; if(n < 4) return 0; hnputl(p, cr->cseq); p += 4; n -= 4; rc4(&cr->current, p, n); cr->cseq += n; return 1; } static int rc4decrypt(OneWay *ow, uchar *p, int n) { CipherRc4 *cr = ow->cipherstate; RC4state tmpstate; ulong seq; long d, dd; if(n < 4) return 0; seq = nhgetl(p); p += 4; n -= 4; d = seq-cr->cseq; if(d == 0) { rc4(&cr->current, p, n); cr->cseq += n; if(cr->ovalid) { dd = cr->cseq - cr->lgseq; if(dd > RC4back) cr->ovalid = 0; } } else if(d > 0) { //print("missing packet: %uld %ld\n", seq, d); // this link is hosed if(d > RC4forward) return 0; cr->lgseq = seq; if(!cr->ovalid) { cr->ovalid = 1; cr->oseq = cr->cseq; memmove(&cr->old, &cr->current, sizeof(RC4state)); } rc4skip(&cr->current, d); rc4(&cr->current, p, n); cr->cseq = seq+n; } else { //print("reordered packet: %uld %ld\n", seq, d); dd = seq - cr->oseq; if(!cr->ovalid || -d > RC4back || dd < 0) return 0; memmove(&tmpstate, &cr->old, sizeof(RC4state)); rc4skip(&tmpstate, dd); rc4(&tmpstate, p, n); return 1; } // move old state up if(cr->ovalid) { dd = cr->cseq - RC4back - cr->oseq; if(dd > 0) { rc4skip(&cr->old, dd); cr->oseq += dd; } } return 1; } static void rc4cipherinit(Conv *c) { uchar key[32]; CipherRc4 *cr; int n; cipherfree(c); n = c->cipher->keylen; if(n > sizeof(key)) n = sizeof(key); /* in */ memset(key, 0, sizeof(key)); setkey(key, n, &c->in, "cipher"); c->in.cipherblklen = 1; c->in.cipherivlen = 4; c->in.cipher = rc4decrypt; cr = smalloc(sizeof(CipherRc4)); memset(cr, 0, sizeof(*cr)); setupRC4state(&cr->current, key, n); c->in.cipherstate = cr; /* out */ memset(key, 0, sizeof(key)); setkey(key, n, &c->out, "cipher"); c->out.cipherblklen = 1; c->out.cipherivlen = 4; c->out.cipher = rc4encrypt; cr = smalloc(sizeof(CipherRc4)); memset(cr, 0, sizeof(*cr)); setupRC4state(&cr->current, key, n); c->out.cipherstate = cr; } static void nullauthinit(Conv *c) { authfree(c); } static void shaauthinit(Conv *c) { authfree(c); } static void seanq_hmac_md5(uchar hash[MD5dlen], ulong wrap, uchar *t, long tlen, uchar *key, long klen) { uchar ipad[65], opad[65], wbuf[4]; int i; DigestState *digest; uchar innerhash[MD5dlen]; for(i=0; i<64; i++){ ipad[i] = 0x36; opad[i] = 0x5c; } ipad[64] = opad[64] = 0; for(i=0; iauthlen) return 0; tlen -= ow->authlen; memset(hash, 0, MD5dlen); seanq_hmac_md5(hash, ow->seqwrap, t, tlen, (uchar*)ow->authstate, 16); r = memcmp(t+tlen, hash, ow->authlen) == 0; memmove(t+tlen, hash, ow->authlen); return r; } static void md5authinit(Conv *c) { int keylen; authfree(c); keylen = c->auth->keylen; if(keylen > 16) keylen = 16; /* in */ c->in.authstate = smalloc(16); memset(c->in.authstate, 0, 16); setkey(c->in.authstate, keylen, &c->in, "auth"); c->in.authlen = 12; c->in.auth = md5auth; /* out */ c->out.authstate = smalloc(16); memset(c->out.authstate, 0, 16); setkey(c->out.authstate, keylen, &c->out, "auth"); c->out.authlen = 12; c->out.auth = md5auth; } static void nullcompinit(Conv *c) { compfree(c); } static int thwackcomp(Conv *c, int, ulong seq, Block **bp) { Block *b, *bb; int nn; ulong ackseq; uchar mask; // add ack info b = padblock(*bp, 4); ackseq = unthwackstate(c->in.compstate, &mask); b->rp[0] = mask; b->rp[1] = ackseq>>16; b->rp[2] = ackseq>>8; b->rp[3] = ackseq; bb = allocb(BLEN(b)); nn = thwack(c->out.compstate, bb->wp, b->rp, BLEN(b), seq, c->lstats.outCompStats); if(nn < 0) { freeb(bb); *bp = b; return ThwackU; } else { bb->wp += nn; freeb(b); *bp = bb; return ThwackC; } } static int thwackuncomp(Conv *c, int subtype, ulong seq, Block **bp) { Block *b, *bb; ulong mask; ulong mseq; int n; switch(subtype) { default: return 0; case ThwackU: b = *bp; 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); return 1; case ThwackC: bb = *bp; b = allocb(ThwMaxBlock); n = unthwack(c->in.compstate, b->wp, ThwMaxBlock, bb->rp, BLEN(bb), seq); freeb(bb); if(n < 0) { if(0)print("unthwack failed: %d\n", n); freeb(b); return 0; } 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; *bp = b; return 1; } } static void thwackcompinit(Conv *c) { compfree(c); c->in.compstate = malloc(sizeof(Unthwack)); if(c->in.compstate == nil) error(Enomem); unthwackinit(c->in.compstate); c->out.compstate = malloc(sizeof(Thwack)); if(c->out.compstate == nil) error(Enomem); thwackinit(c->out.compstate); c->in.comp = thwackuncomp; c->out.comp = thwackcomp; }