Cclientpreedit was a one-bit mask that every producer set as "want ? Cclientpreedit : 0" and every consumer masked out again; nothing else was ever going to join it. Keyreq carries clientpre, an int that says whether the client draws the preedit, and the engine and the XIM context keep it under that name.
549 lines
13 KiB
C
549 lines
13 KiB
C
#include <errno.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "srv.c"
|
|
#include "test.h"
|
|
|
|
#undef recv
|
|
|
|
typedef struct Serverfix Serverfix;
|
|
typedef struct Testclient Testclient;
|
|
|
|
/* A held pump plus a private client-slot channel of the wanted depth. */
|
|
struct Serverfix
|
|
{
|
|
Pump pump;
|
|
Channel *oldclientc;
|
|
};
|
|
|
|
struct Testclient
|
|
{
|
|
int fd;
|
|
int peer;
|
|
dev_t dev;
|
|
ino_t ino;
|
|
Channel *done;
|
|
};
|
|
|
|
static void
|
|
serverbegin(Serverfix *f, int nclients)
|
|
{
|
|
Drawcmd dc;
|
|
uchar token;
|
|
|
|
f->oldclientc = clientc;
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
clientc = chancreate(sizeof token, nclients);
|
|
testengineinit(LangJP);
|
|
pumpstart(&f->pump, 0);
|
|
pumphold(&f->pump, Pumpall);
|
|
}
|
|
|
|
static void
|
|
serverend(Serverfix *f)
|
|
{
|
|
Drawcmd dc;
|
|
|
|
pumpstop(&f->pump);
|
|
chanfree(clientc);
|
|
clientc = f->oldclientc;
|
|
while(channbrecv(drawc, &dc) > 0)
|
|
;
|
|
}
|
|
|
|
static void
|
|
clientproc(void *arg)
|
|
{
|
|
Testclient *client;
|
|
uchar token;
|
|
|
|
client = arg;
|
|
clientthread((void*)(uintptr)client->fd);
|
|
token = 0;
|
|
chansend(client->done, &token);
|
|
}
|
|
|
|
static int
|
|
startclient(struct ct *t, Testclient *client)
|
|
{
|
|
struct stat st;
|
|
int fd[2];
|
|
uchar token;
|
|
|
|
memset(client, 0, sizeof *client);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0)
|
|
return CT_ERRORF(t, "socketpair failed: %s", strerror(errno));
|
|
client->fd = fd[0];
|
|
client->peer = fd[1];
|
|
if(fstat(client->fd, &st) < 0){
|
|
close(client->fd);
|
|
close(client->peer);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
return CT_ERRORF(t, "socket stat failed: %s", strerror(errno));
|
|
}
|
|
client->dev = st.st_dev;
|
|
client->ino = st.st_ino;
|
|
client->done = chancreate(sizeof token, 0);
|
|
token = 0;
|
|
if(channbsend(clientc, &token) <= 0){
|
|
close(client->fd);
|
|
close(client->peer);
|
|
chanfree(client->done);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
client->done = nil;
|
|
return CT_ERRORF(t, "client slot unavailable");
|
|
}
|
|
if(proccreate(clientproc, client, 8192) < 0){
|
|
chanrecv(clientc, &token);
|
|
close(client->fd);
|
|
close(client->peer);
|
|
chanfree(client->done);
|
|
client->fd = -1;
|
|
client->peer = -1;
|
|
client->done = nil;
|
|
return CT_ERRORF(t, "client worker creation failed");
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int
|
|
sendkey(struct ct *t, Testclient *client, int want, u32int mod, u32int key)
|
|
{
|
|
uchar req[Ipcreqsz];
|
|
|
|
ipcpackreq(req, want, mod, key);
|
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
return 1;
|
|
return CT_ERRORF(t, "request send failed: %s", strerror(errno));
|
|
}
|
|
|
|
static int
|
|
sendreset(struct ct *t, Testclient *client, int want)
|
|
{
|
|
uchar req[Ipcreqsz];
|
|
|
|
ipcpackreset(req, want);
|
|
if(ipcsend(client->peer, req, sizeof req) == 0)
|
|
return 1;
|
|
return CT_ERRORF(t, "reset send failed: %s", strerror(errno));
|
|
}
|
|
|
|
static Keyreq
|
|
nextrequestcap(struct ct *t, Pump *p, int op, int cap)
|
|
{
|
|
Keyreq req;
|
|
|
|
memset(&req, 0, sizeof req);
|
|
chanrecv(p->trace, &req);
|
|
CT_EQ_INT(t, op, req.op);
|
|
CT_EQ_INT(t, cap, req.clientpre);
|
|
return req;
|
|
}
|
|
|
|
static Keyreq
|
|
nextrequest(struct ct *t, Pump *p, int op)
|
|
{
|
|
return nextrequestcap(t, p, op, 1);
|
|
}
|
|
|
|
static void
|
|
allowrequest(Pump *p)
|
|
{
|
|
uchar token;
|
|
|
|
token = 0;
|
|
chansend(p->go, &token);
|
|
}
|
|
|
|
static int
|
|
readreply(struct ct *t, Testclient *client, int want, char *wantcommit,
|
|
char *preedit)
|
|
{
|
|
char commit[Ipcfieldmax+1];
|
|
Ipcresp resp;
|
|
|
|
if(ipcreadresp(client->peer, want, commit, preedit, &resp) < 0)
|
|
return CT_ERRORF(t, "response read failed: %s", strerror(errno));
|
|
CT_EQ_STR(t, wantcommit, commit);
|
|
return resp.eaten;
|
|
}
|
|
|
|
static int
|
|
sendframe(struct ct *t, Testclient *client, uchar *frame, int n, int fragment)
|
|
{
|
|
int i, m;
|
|
|
|
for(i = 0; i < n; i += m){
|
|
m = fragment && n-i > 1 ? 1 : n-i;
|
|
if(ipcsend(client->peer, frame+i, m) < 0)
|
|
return CT_ERRORF(t, "frame send failed: %s", strerror(errno));
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
checknoreply(struct ct *t, Testclient *client)
|
|
{
|
|
uchar byte;
|
|
ssize_t n;
|
|
|
|
errno = 0;
|
|
n = recv(client->peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
|
CT_EQ_INT(t, -1, n);
|
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
}
|
|
|
|
static void
|
|
waitclient(struct ct *t, Testclient *client)
|
|
{
|
|
struct stat st;
|
|
uchar token;
|
|
|
|
chanrecv(client->done, &token);
|
|
errno = 0;
|
|
if(fstat(client->fd, &st) == 0)
|
|
CT_CHECK(t, st.st_dev != client->dev || st.st_ino != client->ino);
|
|
else
|
|
CT_EQ_INT(t, EBADF, errno);
|
|
chanfree(client->done);
|
|
client->done = nil;
|
|
}
|
|
|
|
static void
|
|
disconnectclient(struct ct *t, Pump *p, Testclient *client, void *owner)
|
|
{
|
|
Keyreq req;
|
|
uchar token;
|
|
|
|
close(client->peer);
|
|
client->peer = -1;
|
|
req = nextrequest(t, p, Keyrelease);
|
|
if(owner != nil)
|
|
CT_EQ_PTR(t, owner, req.owner);
|
|
CT_CHECK(t, channbrecv(client->done, &token) <= 0);
|
|
allowrequest(p);
|
|
waitclient(t, client);
|
|
}
|
|
|
|
void
|
|
server_connection_ownership(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient a, b, c;
|
|
Keyreq req;
|
|
Str shown;
|
|
char preedit[Ipcfieldmax+1];
|
|
void *aowner, *bowner, *cowner;
|
|
uchar byte, token;
|
|
ssize_t n;
|
|
|
|
memset(&a, 0, sizeof a);
|
|
memset(&b, 0, sizeof b);
|
|
memset(&c, 0, sizeof c);
|
|
a.fd = a.peer = b.fd = b.peer = c.fd = c.peer = -1;
|
|
aowner = bowner = cowner = nil;
|
|
serverbegin(&f, 3);
|
|
if(!startclient(t, &a) || !startclient(t, &b))
|
|
goto cleanup;
|
|
|
|
if(!sendkey(t, &a, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
aowner = req.owner;
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
if(!sendkey(t, &a, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "か", preedit);
|
|
CT_EQ_PTR(t, aowner, testengineowner());
|
|
|
|
if(!sendkey(t, &b, 1, 0, 'n'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
bowner = req.owner;
|
|
CT_CHECK(t, bowner != aowner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
|
|
if(!sendreset(t, &a, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyreset);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, !readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 'y'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "にゃ", preedit);
|
|
|
|
if(!sendreset(t, &b, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyreset);
|
|
CT_EQ_PTR(t, bowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "にゃ", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
|
|
if(!sendkey(t, &b, 0, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequestcap(t, &f.pump, Keypress, 0);
|
|
CT_EQ_PTR(t, bowner, req.owner);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 0, "", preedit));
|
|
errno = 0;
|
|
n = recv(b.peer, &byte, 1, MSG_PEEK|MSG_DONTWAIT);
|
|
CT_EQ_INT(t, -1, n);
|
|
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
|
|
CT_EQ_PTR(t, bowner, testengineowner());
|
|
if(!sendkey(t, &b, 1, 0, 'a'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
allowrequest(&f.pump);
|
|
CT_CHECK(t, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "か", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
testenginepreedit(&shown);
|
|
CT_EQ_INT(t, 0, shown.n);
|
|
|
|
if(!startclient(t, &c))
|
|
goto cleanup;
|
|
if(!sendkey(t, &c, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
cowner = req.owner;
|
|
close(c.peer);
|
|
c.peer = -1;
|
|
allowrequest(&f.pump);
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
CT_EQ_PTR(t, cowner, req.owner);
|
|
CT_EQ_PTR(t, cowner, testengineowner());
|
|
CT_CHECK(t, channbrecv(c.done, &token) <= 0);
|
|
allowrequest(&f.pump);
|
|
waitclient(t, &c);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
CT_CHECK(t, channbrecv(clientc, &token) <= 0);
|
|
|
|
cleanup:
|
|
if(a.peer >= 0)
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
if(b.peer >= 0)
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
if(c.peer >= 0)
|
|
disconnectclient(t, &f.pump, &c, cowner);
|
|
serverend(&f);
|
|
}
|
|
|
|
void
|
|
server_extension_stream(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient a, b;
|
|
Keyreq req;
|
|
uchar frame[Ipccaretsz];
|
|
char preedit[Ipcfieldmax+1];
|
|
void *aowner, *bowner;
|
|
|
|
memset(&a, 0, sizeof a);
|
|
memset(&b, 0, sizeof b);
|
|
a.fd = a.peer = b.fd = b.peer = -1;
|
|
aowner = bowner = nil;
|
|
serverbegin(&f, 2);
|
|
if(!startclient(t, &a))
|
|
goto cleanup;
|
|
|
|
/* Capability and caret frames may be split at any byte boundary. */
|
|
ipcpackcap(frame, 1);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keycap);
|
|
aowner = req.owner;
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
|
|
ipcpackcaret(frame, 1, -101, -7, 23);
|
|
if(!sendframe(t, &a, frame, Ipccaretsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keycaret);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
CT_EQ_INT(t, -101, req.caret.x);
|
|
CT_EQ_INT(t, -7, req.caret.y);
|
|
CT_EQ_INT(t, 23, req.caret.h);
|
|
CT_EQ_PTR(t, nil, testengineowner());
|
|
allowrequest(&f.pump);
|
|
checknoreply(t, &a);
|
|
|
|
ipcpackreq(frame, 1, 0, 'k');
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
CT_EQ_INT(t, -101, req.caret.x);
|
|
CT_EQ_INT(t, -7, req.caret.y);
|
|
CT_EQ_INT(t, 23, req.caret.h);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
CT_EQ_PTR(t, aowner, testengineowner());
|
|
|
|
ipcpackcap(frame, 0);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
goto cleanup;
|
|
req = nextrequestcap(t, &f.pump, Keycap, 0);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "", preedit));
|
|
|
|
/* Reset is still the old six-byte frame and does not discard caret. */
|
|
ipcpackreset(frame, 0);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 1))
|
|
goto cleanup;
|
|
req = nextrequestcap(t, &f.pump, Keyreset, 0);
|
|
CT_EQ_PTR(t, aowner, req.owner);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
CT_EQ_INT(t, -101, req.caret.x);
|
|
CT_EQ_INT(t, -7, req.caret.y);
|
|
CT_EQ_INT(t, 23, req.caret.h);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 0, "k", preedit));
|
|
|
|
ipcpackcap(frame, 1);
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keycap);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "", preedit);
|
|
ipcpackreq(frame, 1, 0, 'n');
|
|
if(!sendframe(t, &a, frame, Ipcreqsz, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
CT_EQ_INT(t, 1, req.caret.valid);
|
|
CT_EQ_INT(t, -101, req.caret.x);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &a, 1, "", preedit));
|
|
CT_EQ_STR(t, "ん", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
|
|
/* Disconnect drops state; a legacy client gets the historical defaults. */
|
|
if(!startclient(t, &b) || !sendkey(t, &b, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
bowner = req.owner;
|
|
CT_EQ_INT(t, 0, req.caret.valid);
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &b, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
|
|
cleanup:
|
|
if(a.peer >= 0)
|
|
disconnectclient(t, &f.pump, &a, aowner);
|
|
if(b.peer >= 0)
|
|
disconnectclient(t, &f.pump, &b, bowner);
|
|
serverend(&f);
|
|
}
|
|
|
|
void
|
|
server_rejects_unknown_extension(struct ct *t)
|
|
{
|
|
Serverfix f;
|
|
Testclient badver, badop, good;
|
|
Keyreq req;
|
|
uchar frame[Ipccaretsz];
|
|
char preedit[Ipcfieldmax+1];
|
|
void *owner;
|
|
|
|
memset(&badver, 0, sizeof badver);
|
|
memset(&badop, 0, sizeof badop);
|
|
memset(&good, 0, sizeof good);
|
|
badver.fd = badver.peer = badop.fd = badop.peer = -1;
|
|
good.fd = good.peer = -1;
|
|
owner = nil;
|
|
serverbegin(&f, 3);
|
|
|
|
if(!startclient(t, &badver))
|
|
goto cleanup;
|
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
frame[1]++;
|
|
if(!sendframe(t, &badver, frame, sizeof frame, 0))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
allowrequest(&f.pump);
|
|
waitclient(t, &badver);
|
|
close(badver.peer);
|
|
badver.peer = -1;
|
|
|
|
if(!startclient(t, &badop))
|
|
goto cleanup;
|
|
ipcpackcaret(frame, 1, 3, 4, 5);
|
|
frame[2]++;
|
|
if(!sendframe(t, &badop, frame, sizeof frame, 1))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keyrelease);
|
|
allowrequest(&f.pump);
|
|
waitclient(t, &badop);
|
|
close(badop.peer);
|
|
badop.peer = -1;
|
|
|
|
/* A malformed peer must not disturb another client connection. */
|
|
if(!startclient(t, &good) || !sendkey(t, &good, 1, 0, 'k'))
|
|
goto cleanup;
|
|
req = nextrequest(t, &f.pump, Keypress);
|
|
owner = req.owner;
|
|
allowrequest(&f.pump);
|
|
CT_EQ_INT(t, 1, readreply(t, &good, 1, "", preedit));
|
|
CT_EQ_STR(t, "k", preedit);
|
|
disconnectclient(t, &f.pump, &good, owner);
|
|
|
|
cleanup:
|
|
if(badver.peer >= 0)
|
|
disconnectclient(t, &f.pump, &badver, nil);
|
|
if(badop.peer >= 0)
|
|
disconnectclient(t, &f.pump, &badop, nil);
|
|
if(good.peer >= 0)
|
|
disconnectclient(t, &f.pump, &good, owner);
|
|
serverend(&f);
|
|
}
|