shithub: rd

ref: baf602582a840a0253900d41a2f1cce7156dd770
dir: /mpas.c/

View raw version
/*
 * Subset of: T.128 Multipoint application sharing
 * 
 * 2.2.8.1.1.1.1 Share Control Header (TS_SHARECONTROLHEADER)
 * http://msdn.microsoft.com/en-us/library/cc240576.aspx
 *
 * totalLen[2] pduType[2] PDUSource[2] 
 *
 * 2.2.9.1.1.3 says there may be many of these.
 */
#include <u.h>
#include <libc.h>
#include <draw.h>
#include "dat.h"
#include "fns.h"
#define DBG if(0)
//#define DBG


uchar	cmap[256];

static const char	srcDesc[] = "Plan 9";	/* sourceDescriptor (T.128 section 8.4.1) */

static void		scanfastpath(uchar*,uchar*);
static void		scancursorpdu(uchar*, uchar*);
static void		scangraphpdu(uchar*,uchar*);
static void		scanimgupdate(uchar*,uchar*);
static void		scanpalette(uchar*,uchar*);
static void		scansrvcapset(uchar*,uchar*);
static void		sendclientinfo(void);
static void		confirmactive(void);
static uchar*	putsdh(uchar*,uchar*,int,int);
static void		assync(void);
static void		asctl(int);
static void		asfontls(void);

enum
{
	Bits4=		0x0F,
	SECHSIZE=	4,
	SCHSIZE=		6,
	SCDSIZE=		SCHSIZE+4+4+2*2,
};

enum /* 2.2.8.1.1.1.1 Share Control Header (TS_SHARECONTROLHEADER) */
{
	PDUTYPE_DEMANDACTIVEPDU	= 1,	/* Demand Active PDU (section 2.2.1.13.1) */
	PDUTYPE_CONFIRMACTIVEPDU	= 3,	/* Confirm Active PDU (section 2.2.1.13.2) */
	PDUTYPE_DEACTIVATEALLPDU	= 6,	/* Deactivate All PDU (section 2.2.3.1) */
	PDUTYPE_DATAPDU				= 7,	/* Data PDU */
	PDUTYPE_SERVER_REDIR_PKT		= 10,	/* Redirection PDU (section 2.2.13.3.1). */
};

enum /* 2.2.8.1.1.1.2 Share Data Header (TS_SHAREDATAHEADER) */
{
	PDUTYPE2_UPDATE=	2,
	PDUTYPE2_CONTROL=	20,
	PDUTYPE2_POINTER=	27,
	PDUTYPE2_INPUT=		28,
	PDUTYPE2_SYNCHRONIZE=	31,
	PDUTYPE2_REFRESH_RECT=	33,
	PDUTYPE2_SUPPRESS_OUTPUT=	35,
	PDUTYPE2_FONTLIST=	39,
	PDUTYPE2_FONTMAP=	40,
	PDUTYPE2_SET_ERROR_INFO_PDU=	47,
};

enum /* 2.2.9.1.1.4 Server Pointer Update PDU (TS_POINTER_PDU) */
{
	TS_PTRMSGTYPE_SYSTEM=	1,
	TS_PTRMSGTYPE_POSITION=	3,
	TS_PTRMSGTYPE_COLOR=	6,
	TS_PTRMSGTYPE_CACHED=	7,
	TS_PTRMSGTYPE_POINTER=	8,
};

enum /* 2.2.9.1.1.3.1.2.2 Bitmap Data */
{
	Bcompress=	1,
	Pcompress=	0x20,
};

enum /* 2.2.1.15.1 Control PDU Data */
{
	CTRLACTION_REQUEST_CONTROL= 	1,
	CTRLACTION_GRANTED_CONTROL=	2,
	CTRLACTION_DETACH=				3,
	CTRLACTION_COOPERATE=			4,
};

enum /* 2.2.1.11.1.1 Info Packet (TS_INFO_PACKET) */
{
	INFO_MOUSE=			0x1,
	INFO_DISABLECTRLALTDEL=	0x2,
	INFO_AUTOLOGON=	0x8,
	INFO_UNICODE=		0x10,
	INFO_MAXIMIZESHELL=	0x20,
	INFO_COMPRESSION=	0x80,
	CompressionTypeMask=	0x1E00,
		PACKET_COMPR_TYPE_8K=	0<<9,	// RDP 4.0 bulk compression ≡ MPPC
		PACKET_COMPR_TYPE_64K=	1<<9,	// RDP 5.0 bulk compression (3.1.8.4.2)
		PACKET_COMPR_TYPE_RDP6=	2<<9,	// RDP 6.0 bulk compression
		PACKET_COMPR_TYPE_RDP61= 3<<9,	// RDP 6.1 bulk compression
	INFO_ENABLEWINDOWSKEY=		0x100,
	INFO_REMOTECONSOLEAUDIO=	0x2000,
	INFO_FORCE_ENCRYPTED_CS_PDU=	0x4000,
	INFO_RAIL=				0x8000,
	INFO_LOGONERRORS=		0x10000,
	INFO_MOUSE_HAS_WHEEL=	0x20000,
	INFO_NOAUDIOPLAYBACK=	0x80000,
	INFO_VIDEO_DISABLE=	0x400000,

	PERF_DISABLE_WALLPAPER=	1<<0,
	PERF_DISABLE_FULLWINDOWDRAG=	1<<1,
	PERF_DISABLE_MENUANIMATIONS=	1<<2,
	PERF_DISABLE_THEMING=	1<<3,
	PERF_DISABLE_CURSOR_SHADOW=		1<<5,
	PERF_DISABLE_CURSORSETTINGS=		1<<6,
	PERF_ENABLE_FONT_SMOOTHING=	1<<7,
};

enum
{
	UPDATETYPE_ORDERS		= 0, 		/* [MS-RDPEGDI] section 2.2.2.2 */
 	UPDATETYPE_BITMAP		= 1,		/* section 2.2.9.1.1.3.1.2 */
	UPDATETYPE_PALETTE		= 2,		/* section 2.2.9.1.1.3.1.1 */
	UPDATETYPE_SYNCHRONIZE	= 3,		/* section 2.2.9.1.1.3.1.3 */
};

enum /* 2.2.9.1.2.1 Fast-Path Update (TS_FP_UPDATE) */
{
	FASTPATH_UPDATETYPE_ORDERS		= 0,	/* [MS-RDPEGDI] section 2.2.2.2 */
	FASTPATH_UPDATETYPE_BITMAP		= 1,
	FASTPATH_UPDATETYPE_PALETTE		= 2,
	FASTPATH_UPDATETYPE_SYNCHRONIZE	= 3,
	FASTPATH_UPDATETYPE_SURFCMDS	= 4,
	FASTPATH_UPDATETYPE_PTR_NULL	= 5,
	FASTPATH_UPDATETYPE_PTR_DEFAULT	= 6,
	FASTPATH_UPDATETYPE_PTR_POSITION	= 8,
	FASTPATH_UPDATETYPE_COLOR		= 9,
	FASTPATH_UPDATETYPE_CACHED		= 10,
	FASTPATH_UPDATETYPE_POINTER		= 11,

};

int
rdphandshake(int)
{
	int i;

	if(mcsconnect(rd.fd) < 0)
		return -1;
	erectdom(rd.fd);
	if(attachuser(rd.fd) < 0)
		return -1;
	if(joinchannel(rd.fd, rd.userchan) < 0)
		return -1;
	if(joinchannel(rd.fd, GLOBALCHAN) < 0)
		return -1;
	for(i = 0; i < nvc; i++)
		if(joinchannel(rd.fd, vctab[i].mcsid) < 0)
			return -1;

	sendclientinfo();
	return rd.fd;
}

void
readnet(int fd)
{
	int chanid, len, flags;
	uchar *p, *ep, buf[MAXTPDU];
	
	for(;;){
		len = readpdu(fd, buf, sizeof(buf));
		if(len <= 0){
			if(rd.active && !rd.hupreason)
				fprint(2, "readpdu: %r\n");
			return;
		}
		p = buf;
		ep = buf+len;
	
		if(istpkt(p,ep) == 0){
			scanfastpath(p, ep);
			continue;
		}
		if(ismcshangup(p,ep)){
			werrstr("Disconnect Provider Ultimatum");
			return;
		}

		chanid = mcschanid(p,ep);
		if(chanid < 0)
			sysfatal("mcschanid: %r");

		p = mcspayload(p, ep);

		flags = GSHORT(p);
		if(!rd.licensed && flags&Slicensepk){
			/*
			 * 2.2.8.1.1.2.1 Basic (TS_SECURITY_HEADER)
			 * http://msdn.microsoft.com/en-us/library/cc240579.aspx
			 */
			p += SECHSIZE;
			if(flags&Slicensepk){
				scanlicensepdu(p, ep);
				continue;
			}
			if(flags&Scrypt)
				sysfatal("legacy encryption of a Slow-Path PDU");
		}

		if(chanid != GLOBALCHAN){
			scanvcpdu(p, ep, chanid);
			continue;
		}

		if(isflowpdu(p,ep))
			continue;

		scanaspdu(p,ep);
	}
}

/* T.128 FlowPDU */
int
isflowpdu(uchar* p, uchar* ep)
{
	int marker;

	if(p+2 > ep){
		werrstr(Eshort);
		return -1;
	}
	marker = GSHORT(p);
	return marker == 0x8000;
}

/* 2.2.9.1.2 Server Fast-Path Update PDU
 * enabled with CanFastpath in General Capability Set
 */
static void
scanfastpath(uchar *p, uchar* ep)
{
	int hd, nb, nord, cflags, ulen, x, y, enc;
	uchar *q, *eq;

	enc = p[0]&(1<<7);
	if(enc)
		sysfatal("legacy encryption in a Fast-Path PDU");
	if(p[1]&(1<<7))
		p += 3;
	else
		p += 2;

	eq = ep;
	while(p+3 < ep){
		/* updateHeader[1] compressionFlags[1]? size[2] updateData[*] */
		hd = *p++;
		if(hd&(1<<7))
			cflags = *p++;
		else
			cflags = 0;
		if(p+2 > ep)
			sysfatal(Eshort);
		nb = GSHORT(p);
		p += 2;
		q = p+nb;

		if(cflags&Pcompress){
			if(p+nb > ep)
				sysfatal(Eshort);
			if((p = uncomp(p, nb, cflags, &ulen)) == nil)
				sysfatal("fast-path packet de-compression failed: %r cflags=%#x", cflags);
			ep = p+ulen;
		}

		switch(hd&Bits4){
		case FASTPATH_UPDATETYPE_ORDERS:
			nord = GSHORT(p);
			scanorders(p+2, ep, nord);
			break;
		case FASTPATH_UPDATETYPE_BITMAP:
			scanimgupdate(p, ep);
			break;
		case FASTPATH_UPDATETYPE_PALETTE:
			scanpalette(p, ep);
			break;
		case FASTPATH_UPDATETYPE_PTR_POSITION:
			x = GSHORT(p+0);
			y = GSHORT(p+2);
			warpmouse(x, y);
			break;
		}

		p = q;
		ep = eq;
	}

	lockdisplay(display);
	flushimage(display, 1);
	unlockdisplay(display);
}

/* T.128 ASPDU */
void
scanaspdu(uchar* p, uchar* ep)
{
	int pdutype, len;

	while(p+SCHSIZE <= ep){
		len = GSHORT(p);
		if(len < SCHSIZE || p+len > ep)
			sysfatal("bad length in Share Control PDU header");
	
		pdutype = GSHORT(p+2)&Bits4;

		switch(pdutype){
		case PDUTYPE_DEMANDACTIVEPDU:
			activating(p+SCHSIZE, p+len);
			rd.active = 1;
			break;
		case PDUTYPE_DATAPDU:
			scandatapdu(p+SCHSIZE, p+len);
			break;
		case PDUTYPE_DEACTIVATEALLPDU:
			rd.active = 0;
			break;
		}
		p += len;
	}
}

/*
 * 2.2.8.1.1.1.2 Share Data Header (TS_SHAREDATAHEADER)
 * http://msdn.microsoft.com/en-us/library/cc240577.aspx
 *
 * shareId[4] pad1[1] streamId[1] uncomprLen[2]
 * pduType2[1] comprType[1] comprLen[2]
 */
void
scandatapdu(uchar *p, uchar* ep)
{
	int pduType2, ctype, clen, ulen, ulenr;

	ulen = GSHORT(p+6);
	pduType2 = p[8];
	ctype = p[9];
	clen = GSHORT(p+10);
	p += 12;

	if(ctype&(1<<5)){
		clen -= SCDSIZE;
		if(p+clen > ep)
			sysfatal(Eshort);
		if((p = uncomp(p, clen, ctype, &ulenr)) == nil)
			sysfatal("decompression failed: %r");
		if(ulen != ulenr+SCDSIZE)
			sysfatal("bad length after decompression");
		ep = p+ulenr;
	}

	switch (pduType2){
	case PDUTYPE2_SYNCHRONIZE:
	case PDUTYPE2_CONTROL:
	case PDUTYPE2_FONTMAP:	/* denotes completion of the connection sequence */
		break;
	case PDUTYPE2_SET_ERROR_INFO_PDU:
		/* 2.2.5.1.1 Set Error Info PDU Data (TS_SET_ERROR_INFO_PDU) */
		rd.hupreason = GLONG(p);
		break;
	case PDUTYPE2_UPDATE:
		scangraphpdu(p, ep);
		break;
	case PDUTYPE2_POINTER:
		scancursorpdu(p, ep);
		break;
	}
}

/* 2.2.9.1.1.3.1 Slow-Path Graphics Update (TS_GRAPHICS_UPDATE) */
static void
scangraphpdu(uchar *p, uchar *ep)
{
	int uptype, nord;

	if(p+2 > ep)
		sysfatal(Eshort);

	uptype = GSHORT(p);
	switch(uptype){
	case UPDATETYPE_ORDERS:
		if(p+8 > ep)
			sysfatal(Eshort);
		nord = GSHORT(p+4);
		scanorders(p+8, ep, nord);
		break;
	case UPDATETYPE_BITMAP:
		scanimgupdate(p, ep);
		break;
	case UPDATETYPE_PALETTE:
		scanpalette(p, ep);
		break;
	}
	lockdisplay(display);
	flushimage(display, 1);
	unlockdisplay(display);
}

/* 2.2.9.1.1.4 Server Pointer Update PDU (TS_POINTER_PDU) */
static void
scancursorpdu(uchar* p, uchar* ep)
{
	int type, x, y;

	if(p+2 > ep)
		sysfatal(Eshort);
	type = GSHORT(p);
	switch(type){
	case TS_PTRMSGTYPE_POSITION:
		if(p+8 > ep)
			sysfatal(Eshort);
		x = GSHORT(p+4);
		y = GSHORT(p+6);
		warpmouse(x, y);
		break;
	}
}

/* 2.2.9.1.1.3.1.2.1 Bitmap Update Data (TS_UPDATE_BITMAP_DATA) */
static void
scanimgupdate(uchar* p, uchar* ep)
{
	uchar *s;
	int err, nr, len, depth, chan, opt;
	static Image* img;
	Rectangle r, rs, d;

	if(p+4 > ep)
		sysfatal(Eshort);
	chan = rd.chan;
	rs = rectaddpt(Rpt(ZP, rd.dim), screen->r.min);
	nr = GSHORT(p+2);
	p += 4;

	lockdisplay(display);

	if(img==nil || !eqrect(img->r, rs)){
		if(img != nil)
			freeimage(img);
		img = allocimage(display, rs, chan, 0, DNofill);
		if(img == nil)
			sysfatal("%r");
	}

	for(; nr>0 && p+18<ep; nr--){
		/* 2.2.9.1.1.3.1.2.2 Bitmap Data (TS_BITMAP_DATA) */
		d.min.x =	GSHORT(p+0);
		d.min.y =	GSHORT(p+2);
		d.max.x =	GSHORT(p+4) + 1;
		d.max.y =	GSHORT(p+6) + 1;
		r.min = ZP;
		r.max.x =	GSHORT(p+8);
		r.max.y =	GSHORT(p+10);
		depth = 	GSHORT(p+12);
		opt = 	GSHORT(p+14);
		len =   	GSHORT(p+16);
		p +=    	18;
		s = p+len;
		if(s > ep)
			sysfatal(Eshort);
		if(depth != img->depth)
			sysfatal("bad image depth");
		r = rectaddpt(r, img->r.min);

		if(opt&Bcompress)
		if(!(opt&NoBitcomphdr))
			p += 8;

		err = (opt&Bcompress? loadrle : loadbmp)(img, r, p, s-p);
		if(err < 0)
			sysfatal("%r");
		draw(screen, rectaddpt(d, screen->r.min), img, nil, img->r.min);
		p = s;
	}
	unlockdisplay(display);
}

static void
scanpalette(uchar* p, uchar* ep)
{
	int i, n;

	n = GSHORT(p+4);
	p += 8;
	if(n > sizeof(cmap)){
		fprint(2, "scanpalette: palette too big");
		return;
	}
	if(p+3*n > ep)
		sysfatal(Eshort);
	for(i = 0; i<n; i++, p+=3)
		cmap[i] = rgb2cmap(p[0], p[1], p[2]);
}

static void
scansrvcapset(uchar *p, uchar *ep)
{
	int ncap, type, len;

	ncap = GSHORT(p);
	p += 4;
	for(; ncap>0 && p+4<ep; ncap--){
		type = GSHORT(p+0);
		len = GSHORT(p+2);
		if(p+len > ep)
			sysfatal("bad length in server's capability set");
		switch (type){
		case CapGeneral:
			scangencaps(p, p+len);
			break;
		case CapBitmap:
			scanbitcaps(p, p+len);
			break;
		}
		p += len;
	}
}

/*
 * 2.2.1.13.1 Server Demand Active PDU
 * http://msdn.microsoft.com/en-us/library/cc240484.aspx
 */
void
activating(uchar* p, uchar* ep)
{
	int nsrc, ncaps;

	rd.shareid = GLONG(p);
	nsrc = GSHORT(p+4);
	ncaps = GSHORT(p+6);
	p += 8+nsrc;
	if(p+ncaps >= ep){
		werrstr(Eshort);
		return;
	}
	scansrvcapset(p, p+ncaps);
	confirmactive();
	// server accepts input since this point
	passinput(0, InputSync, 0, 0, 0);

	assync();
	asctl(CTRLACTION_COOPERATE);
	asctl(CTRLACTION_REQUEST_CONTROL);
	asfontls();	// unleashes the artist
}

/* 2.2.1.13.2 Client Confirm Active PDU */
static void
confirmactive(void)
{
	int ncap, nsrc, capsize, calen, pdusize;
	uchar buf[512], *p, *q, *ep;

	ncap = 8;
	nsrc = sizeof(srcDesc);
	capsize = 0
		+ GENCAPSIZE
		+ BITCAPSIZE
		+ ORDCAPSIZE
		+ BCACAPSIZE
		+ PTRCAPSIZE
		+ INPCAPSIZE
		+ SNDCAPSIZE
		+ GLYCAPSIZE
		;
	calen = 20+nsrc+capsize;

	p = prebuf(buf, sizeof(buf), calen, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	ep = p+calen;
	pdusize = ep-buf;
	q = p;

	/* 2.2.8.1.1.1.1 Share Control Header */
	/* totalLength[2] pduType[2] PDUSource[2] */
	PSHORT(p+0, calen);
	PSHORT(p+2, PDUTYPE_CONFIRMACTIVEPDU | (1<<4));
	PSHORT(p+4, rd.userchan);

	/* shareId[4] originatorId[2] sdlen[2] caplen[2] srcdesc[sdlen] ncap[2] pad[2] */
	PLONG(p+6, rd.shareid);
	PSHORT(p+10, SRVCHAN);
	PSHORT(p+12, nsrc);
	PSHORT(p+14, capsize+4);
	memcpy(p+16, srcDesc, nsrc);
	PSHORT(p+16+nsrc, ncap);
	PSHORT(p+18+nsrc, 0);
	p += nsrc+20;
	p = putgencaps(p, ep);
	p = putbitcaps(p, ep);
	p = putordcaps(p, ep);
	p = putbc2caps(p, ep);
	p = putptrcaps(p, ep);
	p = putinpcaps(p, ep);
	p = putsndcaps(p, ep);
	p = putglycaps(p, ep);
	assert(p-calen == q);

	writen(rd.fd, buf, pdusize);
}

/* 2.2.1.11 Client Info PDU */
static void
sendclientinfo(void)
{
	uchar a[1024], *p, *q;
	int ndata, secflags, usize;
	int opt, perfopt;
	int ndom, nusr, npw, nsh, nwd;
	uchar *wdom, *wusr, *wpw, *wsh, *wwd;

	ndom = strlen(rd.windom)+1;
	nusr = strlen(rd.user)+1;
	npw = strlen(rd.passwd)+1;
	nsh = strlen(rd.shell)+1;
	nwd = strlen(rd.rwd)+1;
	wdom = emalloc(4*ndom);
	wusr = emalloc(4*nusr);
	wpw = emalloc(4*npw);
	wsh = emalloc(4*nsh);
	wwd = emalloc(4*nwd);
	ndom = toutf16(wdom, 4*ndom, rd.windom, ndom);
	nusr = toutf16(wusr, 4*nusr, rd.user, nusr);
	npw = toutf16(wpw, 4*npw, rd.passwd, npw);
	nsh = toutf16(wsh, 4*nsh, rd.shell, nsh);
	nwd = toutf16(wwd, 4*nwd, rd.rwd, nwd);

	ndata = 18+ndom+nusr+npw+nsh+nwd+188;
	
	opt = 0
	| INFO_MOUSE
	| INFO_UNICODE
	| INFO_DISABLECTRLALTDEL
	| INFO_MAXIMIZESHELL
	| INFO_ENABLEWINDOWSKEY
	| INFO_FORCE_ENCRYPTED_CS_PDU
	| INFO_COMPRESSION
	| PACKET_COMPR_TYPE_8K
	| PACKET_COMPR_TYPE_64K
	;
	perfopt = 0
	| PERF_DISABLE_FULLWINDOWDRAG
	| PERF_DISABLE_MENUANIMATIONS
	| PERF_DISABLE_CURSORSETTINGS
	| PERF_DISABLE_THEMING
	;
	if(rd.autologon)
		opt |= INFO_AUTOLOGON;

	secflags = Sinfopk;
	p = prebuf(a, sizeof(a), ndata, 0, secflags);
	if(p == nil)
		sysfatal("sendclientinfo: %r");
	usize = p+ndata-a;
	q = p;

	PLONG(q+0, 0);	// codePage; langId when opt&INFO_UNICODE
	PLONG(q+4, opt);
	PSHORT(q+8, ndom-2);
	PSHORT(q+10, nusr-2);
	PSHORT(q+12, npw-2);
	PSHORT(q+14, nsh-2);
	PSHORT(q+16, nwd-2);
	q += 18;
	memcpy(q, wdom, ndom);
	q += ndom;
	memcpy(q, wusr, nusr);
	q += nusr;
	memcpy(q, wpw, npw);
	q += npw;
	memcpy(q, wsh, nsh);
	q += nsh;
	memcpy(q, wwd, nwd);
	q += nwd;

	PSHORT(q+0, 2);	// cbClientAddress 
	PSHORT(q+2, 0);	// clientAddress
	PSHORT(q+4, 2);	// cbClientDir
	PSHORT(q+6, 0);	// clientDir
	memset(q+8, 172, 0);	// clientTimeZone
	PLONG(q+180, 0);	// clientSessionId
	PLONG(q+184, perfopt);	// performanceFlags 
	q += 188;

	assert(q == p+ndata);

	free(wdom);
	free(wusr);
	free(wpw);
	free(wsh);
	free(wwd);

	writen(rd.fd, a, usize);
	
}

/* Share-Data Header (2.2.8.1.1.1.2 Share Data Header) */
static uchar*
putsdh(uchar* p, uchar* ep, int ndata, int pduType2)
{
	if(p+18>ep)
		sysfatal(Eshort);
	PSHORT(p+0, ndata);
	PSHORT(p+2, (PDUTYPE_DATAPDU | 0x10));
	PSHORT(p+4, rd.userchan);
	PLONG(p+6, rd.shareid);
	p[10] = 0;
	p[11] = 1;
	PSHORT(p+12, ndata);	// rdesktop used to put ndata-14...
	p[14] = pduType2;
	p[15] = 0; // ctype
	PSHORT(p+16, 0); // clen
	return p+18;
}

/* 2.2.1.14 Client Synchronize PDU */
static void
assync(void)
{
	uchar a[64], *p, *q;
	int ndata, usize; 

	ndata = 4+SCDSIZE;
	p = prebuf(a, sizeof(a), ndata, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	usize = p+ndata-a;

	q = putsdh(p, p+ndata, ndata, PDUTYPE2_SYNCHRONIZE);
	PSHORT(q+0, 1);
	PSHORT(q+2, 1002);	// target MCS userId
	writen(rd.fd, a, usize);
}

/* 2.2.1.15.1 Control PDU Data (TS_CONTROL_PDU) */
static void
asctl(int action)
{
	uchar a[64], *p, *q;
	int ndata, usize; 

	ndata = 8+SCDSIZE;
	p = prebuf(a, sizeof(a), ndata, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	usize = p+ndata-a;

	q = putsdh(p, p+ndata, ndata, PDUTYPE2_CONTROL);
	PSHORT(q+0, action);
	PSHORT(q+2, 0);	// grantId[2]
	PLONG(q+4, 0); 	// controlId[2]
	writen(rd.fd, a, usize);
}

/* 2.2.1.18 Client Font List PDU */
static void
asfontls(void)
{
	uchar a[64], *p, *q;
	int ndata, usize; 

	ndata = 8+SCDSIZE;
	p = prebuf(a, sizeof(a), ndata, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	usize = p+ndata-a;

	q = putsdh(p, p+ndata, ndata, PDUTYPE2_FONTLIST);
	PSHORT(q+0, 0);	// numberFonts
	PSHORT(q+2, 0);	// totalNumFonts
	PSHORT(q+4, 2+1);	// listFlags: 1=first, 2=last
	PSHORT(q+6, 50);	// entrySize

	writen(rd.fd, a, usize);
}

/* 2.2.8.1.1.3.1.1 Slow-Path Input Event (TS_INPUT_EVENT) */
void
passinput(ulong msec, int mtype, int iflags, int iarg1, int iarg2)
{
	uchar a[64], *p, *q;
	int ndata, usize; 

	ndata = 16+SCDSIZE;
	p = prebuf(a, sizeof(a), ndata, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	usize = p+ndata-a;

	q = putsdh(p, p+ndata, ndata, PDUTYPE2_INPUT);
	PSHORT(q+0, 1); // numEvents
	PSHORT(q+2, 0);
	// 2.2.8.1.1.3.1.1 Slow-Path Input Event
	PLONG(q+4, msec);
	PSHORT(q+8, mtype);
	// slowPathInputData[*]
	PSHORT(q+10, iflags);
	PSHORT(q+12, iarg1);
	PSHORT(q+14, iarg2);

	writen(rd.fd, a, usize);
}

/* 2.2.11.3.1 Suppress Output PDU Data (TS_SUPPRESS_OUTPUT_PDU) */
void
turnupdates(int allow)
{
	uchar a[64], *p, *q;
	int ndata, usize; 

	ndata = (allow?12:4)+SCDSIZE;
	p = prebuf(a, sizeof(a), ndata, 0, 0);
	if(p == nil)
		sysfatal("buffer not prepared: %r");
	usize = p+ndata-a;

	q = putsdh(p, p+ndata, ndata, PDUTYPE2_SUPPRESS_OUTPUT);
	q[0] = (allow?1:0); 
	memset(q+1, 3, 0);
	if(allow){
		PSHORT(q+4, 0);	// left
		PSHORT(q+6, 0);	// top
		PSHORT(q+8, rd.dim.x-1);	// right
		PSHORT(q+10, rd.dim.y-1);	// bottom
	}
	writen(rd.fd, a, usize);
}

int
sizesechdr(int secflags)
{
	if(secflags&Scrypt)
		return  12;	// flags[4] mac[8]
	else if(secflags)
		return 4;		// flags[4]
	return 0;
}

uchar*
prebuf(uchar* buf, int nb, int ndata, int chanid, int secflags)
{
	int n, len, shdsize;
	uchar *p, *ep;

	if(chanid==0)
		chanid = GLOBALCHAN;

	shdsize = sizesechdr(secflags);
	len = TPDATAFIXLEN+8+shdsize+ndata;
	if(len>nb){
		werrstr("%s: provided %d, need %d, data %d", Esmall, nb, len, ndata);
		return nil;
	}
	ep = buf+len;

	ndata = len-TPDATAFIXLEN;
	n = mktpdat(buf, nb, ndata);
	if(n < 0)
		sysfatal("mktpdat: %r");
	p = buf+TPDATAFIXLEN;

	ndata -= 8;
	if(mkmcssdr(p, ep-p, ndata, chanid) < 0)
		sysfatal("mkmcssdr: %r");
	p += 8;

	if(shdsize > 0)
		PLONG(p, secflags);
	return p + shdsize;
}