#define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include "ipc.h" #include "live.h" enum { Maxclients = 64, Cappreedit = 1, }; typedef struct Response Response; struct Response { int eaten; char commit[Ipcfieldmax+1]; char preedit[Ipcfieldmax+1]; }; static size_t getlen(unsigned char p[Ipclensz]) { return p[0] | (p[1] << 8); } static int readresponseuntil(int fd, int want, Response *res, int64_t deadline, int quiet) { unsigned char hdr[Ipcresphdrsz], len[Ipclensz]; size_t n; int rv; memset(res, 0, sizeof *res); rv = readuntil(fd, hdr, sizeof hdr, deadline); if(rv <= 0){ if(!quiet) fail("read response header: %s", rv == 0 ? "peer closed" : strerror(errno)); return rv; } res->eaten = hdr[0] != 0; n = getlen(hdr + 1); if(n > Ipcfieldmax){ fail("invalid commit length %zu", n); return -1; } if(n != 0){ rv = readuntil(fd, res->commit, n, deadline); if(rv <= 0){ if(!quiet) fail("read commit: %s", rv == 0 ? "peer closed" : strerror(errno)); return rv; } } res->commit[n] = '\0'; if(!want) return 1; rv = readuntil(fd, len, sizeof len, deadline); if(rv <= 0){ if(!quiet) fail("read preedit length: %s", rv == 0 ? "peer closed" : strerror(errno)); return rv; } n = getlen(len); if(n > Ipcfieldmax){ fail("invalid preedit length %zu", n); return -1; } if(n != 0){ rv = readuntil(fd, res->preedit, n, deadline); if(rv <= 0){ if(!quiet) fail("read preedit: %s", rv == 0 ? "peer closed" : strerror(errno)); return rv; } } res->preedit[n] = '\0'; return 1; } static int sendkey(int fd, int want, uint32_t mod, uint32_t key) { unsigned char req[Ipcreqsz]; ipcpackreq(req, want, mod, key); return ipcsend(fd, req, sizeof req) == 0; } static int sendreset(int fd, int want) { unsigned char req[Ipcreqsz]; ipcpackreset(req, want); return ipcsend(fd, req, sizeof req) == 0; } static int sendcap(int fd, int cap) { unsigned char req[Ipcreqsz]; ipcpackcap(req, cap); return ipcsend(fd, req, sizeof req) == 0; } static int expectresponse(int fd, int want, int eaten, char *commit, char *preedit, char *where) { Response res; if(readresponseuntil(fd, want, &res, nowms() + Calltimeout, 0) != 1) return 0; if(res.eaten != eaten || strcmp(res.commit, commit) != 0 || (want && strcmp(res.preedit, preedit) != 0)) return fail("%s: eaten=%d commit=%s preedit=%s", where, res.eaten, res.commit, res.preedit); return 1; } static int requestreset(int fd, int want, int eaten, char *commit, char *preedit, char *where) { if(!sendreset(fd, want)) return fail("send %s: %s", where, strerror(errno)); return expectresponse(fd, want, eaten, commit, preedit, where); } static int requestkey(int fd, int want, uint32_t mod, uint32_t key, int eaten, char *commit, char *preedit, char *where) { if(!sendkey(fd, want, mod, key)) return fail("send %s: %s", where, strerror(errno)); return expectresponse(fd, want, eaten, commit, preedit, where); } static int tryclient(char *path, int64_t deadline, int *client) { Response res; int fd, rv; fd = connectsocket(path, deadline); if(fd < 0) return errno == ETIMEDOUT ? -1 : 0; if(!sendkey(fd, 1, 0, Kmodfirst)){ close(fd); return 0; } rv = readresponseuntil(fd, 1, &res, deadline, 1); if(rv != 1){ close(fd); return rv < 0 && errno == ETIMEDOUT ? -1 : 0; } if(res.eaten || res.commit[0] != '\0' || res.preedit[0] != '\0'){ close(fd); fail("replacement client returned invalid modifier response"); return -1; } *client = fd; return 1; } static int waitslot(char *path, int *client, char *where) { int rv; int64_t deadline; deadline = nowms() + Calltimeout; for(;;){ rv = tryclient(path, deadline, client); if(rv > 0) return 1; if(rv < 0 || leftms(deadline) == 0) return fail("%s: timed out waiting for a worker slot", where); } } static int overflowrejected(char *path) { struct pollfd pfd; unsigned char byte; ssize_t n; int fd, timeout; int64_t deadline; deadline = nowms() + Calltimeout; fd = connectsocket(path, deadline); if(fd < 0) return fail("overflow connect: %s", strerror(errno)); if(!sendkey(fd, 1, 0, Kmodfirst)){ close(fd); return 1; } for(;;){ timeout = leftms(deadline); if(timeout == 0){ close(fd); return fail("overflow client was not rejected"); } pfd.fd = fd; pfd.events = POLLIN; pfd.revents = 0; n = poll(&pfd, 1, timeout); if(n < 0 && errno == EINTR) continue; if(n <= 0){ close(fd); return fail("poll overflow client: %s", n == 0 ? "timed out" : strerror(errno)); } n = recv(fd, &byte, 1, 0); if(n < 0 && errno == EINTR) continue; if(n == 0 || (n < 0 && (errno == ECONNRESET || errno == EPIPE))){ close(fd); return 1; } if(n < 0){ close(fd); return fail("read overflow rejection: %s", strerror(errno)); } close(fd); return fail("overflow client received a response"); } } static int runsmoke(char *path) { int client, ok; client = connectsocket(path, nowms() + Calltimeout); if(client < 0) return fail("connect client: %s", strerror(errno)); ok = sendcap(client, Cappreedit) && expectresponse(client, 1, 1, "", "", "negotiate preedit") && requestkey(client, 1, Mctrl, 'n', 1, "", "", "select Japanese") && requestkey(client, 1, 0, 'k', 1, "", "k", "preedit") && requestreset(client, 1, 1, "k", "", "reset"); close(client); return ok; } static int runcapacity(char *path) { int client[Maxclients]; int i, ok; for(i = 0; i < Maxclients; i++) client[i] = -1; ok = 0; client[0] = connectsocket(path, nowms() + Calltimeout); if(client[0] < 0){ fail("connect first client: %s", strerror(errno)); goto out; } if(!requestkey(client[0], 1, Mctrl, 'n', 1, "", "", "select Japanese")) goto out; for(i = 1; i < Maxclients; i++){ client[i] = connectsocket(path, nowms() + Calltimeout); if(client[i] < 0){ fail("connect capacity client %d: %s", i, strerror(errno)); goto out; } if(!requestkey(client[i], 1, 0, Kmodfirst, 0, "", "", "capacity modifier")) goto out; } if(!overflowrejected(path)) goto out; close(client[Maxclients-1]); client[Maxclients-1] = -1; if(!waitslot(path, &client[Maxclients-1], "inactive slot recovery")) goto out; close(client[0]); client[0] = -1; if(!waitslot(path, &client[0], "active slot recovery") || !requestkey(client[0], 1, 0, 'a', 1, "", "あ", "post-disconnect composition")) goto out; ok = 1; out: for(i = 0; i < Maxclients; i++) if(client[i] >= 0) close(client[i]); return ok; } int main(int argc, char **argv) { Daemon daemon; Live live; int capacity, ok; testname = "ipc_live_test"; capacity = argc == 4 && strcmp(argv[1], "--capacity") == 0; if((!capacity && argc != 3) || (capacity && argc != 4)){ fprintf(stderr, "usage: ipc_live_test [--capacity] strans mapdir\n"); return 2; } daemoninit(&daemon, "daemon"); ok = livesetup(&live, "ipc") && startdaemon(&live, &daemon, argv[1+capacity], argv[2+capacity]) && waitready(&live, &daemon, NULL, 0); if(ok) ok = capacity ? runcapacity(live.socket) : runsmoke(live.socket); if(!ok) showerrors(&daemon); if(!stopdaemon(&daemon)) ok = 0; if(!closeerrors(&daemon)) ok = 0; if(!liveclean(&live)) ok = 0; if(!ok) return 1; printf("ipc %s: ok\n", capacity ? "connection capacity" : "live smoke"); return 0; }