From 6a58b63223bfe6c35961c732052056bcf7edac63 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Fri, 14 Aug 2026 18:53:14 +0900 Subject: [PATCH] ipc: negotiate preedit capability and caret messages --- ipc.c | 103 +++++++++++++++++ ipc.h | 23 +++- srv.c | 71 +++++++++--- tests/ipc_test.c | 84 ++++++++++++++ tests/server_test.c | 268 +++++++++++++++++++++++++++++++++++++++++++- tests/test.h | 3 + tests/unit_test.c | 3 + 7 files changed, 538 insertions(+), 17 deletions(-) diff --git a/ipc.c b/ipc.c index 54f8e6c..1b31549 100644 --- a/ipc.c +++ b/ipc.c @@ -20,6 +20,32 @@ getlen(const unsigned char p[Ipclensz]) return p[0] | (p[1] << 8); } +static void +put32(unsigned char *p, int32_t v) +{ + uint32_t u; + + u = v; + p[0] = u; + p[1] = u >> 8; + p[2] = u >> 16; + p[3] = u >> 24; +} + +static int32_t +get32(const unsigned char *p) +{ + uint32_t u; + + u = (uint32_t)p[0] | + ((uint32_t)p[1] << 8) | + ((uint32_t)p[2] << 16) | + ((uint32_t)p[3] << 24); + if(u <= INT32_MAX) + return u; + return -(int32_t)(~u) - 1; +} + int ipcpath(char *dst, size_t cap) { @@ -82,6 +108,37 @@ ipcpackreset(unsigned char req[Ipcreqsz], int want) req[0] = Ipcreqreset | (want ? Ipcreqwant : 0); } +/* An old server sees this reserved frame as a key-zero request. */ +void +ipcpackcap(unsigned char req[Ipcreqsz], int want) +{ + memset(req, 0, Ipcreqsz); + req[0] = Ipcext | (want ? Ipcreqwant : 0); + req[1] = Ipcversion; + req[2] = Ipcopcap; +} + +int +ipcpackcaret(unsigned char req[Ipccaretsz], int valid, int32_t x, + int32_t y, int32_t h) +{ + if(valid != 0 && valid != 1) + return -1; + if(valid && h < 0) + return -1; + memset(req, 0, Ipccaretsz); + req[0] = Ipcext; + req[1] = Ipcversion; + req[2] = Ipcopcaret; + req[3] = valid; + if(!valid) + return 0; + put32(req + 4, x); + put32(req + 8, y); + put32(req + 12, h); + return 0; +} + void ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod, uint32_t *key) @@ -94,6 +151,52 @@ ipcunpackreq(const unsigned char req[Ipcreqsz], int *want, uint32_t *mod, ((uint32_t)req[5] << 24); } +int +ipcreqtype(const unsigned char req[Ipcreqsz]) +{ + if((req[0] & Ipcext) == 0) + return Ipclegacy; + if(req[1] != Ipcversion) + return Ipcunknown; + switch(req[2]){ + case Ipcopcap: + if((req[0] & ~(Ipcext|Ipcreqwant)) != 0 || + req[3] != 0 || req[4] != 0 || req[5] != 0) + return Ipcunknown; + return Ipccap; + case Ipcopcaret: + if(req[0] != Ipcext || req[3] > 1) + return Ipcunknown; + return Ipccaret; + } + return Ipcunknown; +} + +int +ipcunpackcaret(const unsigned char req[Ipccaretsz], int *valid, + int32_t *x, int32_t *y, int32_t *h) +{ + int32_t wireh; + + if(valid == NULL || x == NULL || y == NULL || h == NULL || + ipcreqtype(req) != Ipccaret) + return -1; + wireh = get32(req + 12); + if(wireh < 0) + return -1; + *valid = req[3]; + if(!*valid){ + *x = 0; + *y = 0; + *h = 0; + return 0; + } + *x = get32(req + 4); + *y = get32(req + 8); + *h = wireh; + return 0; +} + int ipcreqreset(const unsigned char req[Ipcreqsz]) { diff --git a/ipc.h b/ipc.h index 2fcc82c..e379c66 100644 --- a/ipc.h +++ b/ipc.h @@ -2,7 +2,7 @@ #include /* - * Request: [flags, modifiers, key byte 0, ..., key byte 3]. + * Legacy request: [flags, modifiers, key byte 0, ..., key byte 3]. * Flags request preedit and distinguish lifecycle reset from physical Escape. * The server identifies the connection as the engine owner; that identity is * not sent on the wire. @@ -11,12 +11,20 @@ * followed, when requested, by * [preedit-length-low, preedit-length-high, preedit...]. * Lengths are little-endian byte counts; fields are at most Ipcfieldmax bytes. + * + * Capability control is six bytes: [0x80|want, version, 0, 0, 0, 0]. + * Caret control is sixteen bytes: [0x80, version, 1, valid, x, y, h], + * with the signed coordinates and height encoded as little-endian 32-bit words. */ enum { Ipcreqwant = 1<<0, Ipcreqreset = 1<<1, + Ipcext = 1<<7, + Ipcversion = 1, + Ipcopcap = 0, + Ipcopcaret = 1, Kspec = 0x110000, Kback = Kspec|0x08, @@ -35,12 +43,21 @@ enum Mmask = Mshift|Mctrl|Malt|Msuper, Ipcreqsz = 6, + Ipccaretsz = 16, Ipclensz = 2, Ipcresphdrsz = 1 + Ipclensz, Ipcfieldmax = 256, Ipcmaxresp = Ipcresphdrsz + Ipcfieldmax + Ipclensz + Ipcfieldmax, }; +enum +{ + Ipcunknown = -1, + Ipclegacy, + Ipccap, + Ipccaret, +}; + typedef struct Ipcresp Ipcresp; struct Ipcresp { @@ -51,7 +68,11 @@ struct Ipcresp void ipcpackreq(unsigned char[Ipcreqsz], int, uint32_t, uint32_t); void ipcpackreset(unsigned char[Ipcreqsz], int); +void ipcpackcap(unsigned char[Ipcreqsz], int); +int ipcpackcaret(unsigned char[Ipccaretsz], int, int32_t, int32_t, int32_t); void ipcunpackreq(const unsigned char[Ipcreqsz], int*, uint32_t*, uint32_t*); +int ipcunpackcaret(const unsigned char[Ipccaretsz], int*, int32_t*, int32_t*, int32_t*); +int ipcreqtype(const unsigned char[Ipcreqsz]); int ipcreqreset(const unsigned char[Ipcreqsz]); int ipcpackresp(unsigned char*, size_t, int, const char*, size_t, const char*, size_t, int); int ipcreadn(int, void*, size_t); diff --git a/srv.c b/srv.c index 4178069..4a58d68 100644 --- a/srv.c +++ b/srv.c @@ -8,20 +8,46 @@ static char adir[256]; static Channel *clientc; static int -srvreadkey(int fd, Keyreq *kr) +srvreadreq(int fd, Keyreq *kr, int *want) { - uchar req[Ipcreqsz]; + uchar req[Ipccaretsz]; + int type, valid; + int32_t x, y, h; u32int ks, mod; - int want; - if(ipcreadn(fd, req, sizeof req) < 0) + if(ipcreadn(fd, req, Ipcreqsz) < 0) return -1; - ipcunpackreq(req, &want, &mod, &ks); - kr->op = ipcreqreset(req) ? Keyreset : Keypress; - kr->ks = ks; - kr->mod = mod; - memset(&kr->caret, 0, sizeof kr->caret); - return want; + type = ipcreqtype(req); + switch(type){ + case Ipclegacy: + ipcunpackreq(req, want, &mod, &ks); + kr->op = ipcreqreset(req) ? Keyreset : Keypress; + kr->ks = ks; + kr->mod = mod; + break; + case Ipccap: + *want = (req[0] & Ipcreqwant) != 0; + kr->op = Keycap; + kr->ks = 0; + kr->mod = 0; + break; + case Ipccaret: + if(ipcreadn(fd, req + Ipcreqsz, Ipccaretsz - Ipcreqsz) < 0 || + ipcunpackcaret(req, &valid, &x, &y, &h) < 0) + return -1; + *want = 0; + kr->op = Keycaret; + kr->ks = 0; + kr->mod = 0; + kr->caret.valid = valid; + kr->caret.x = x; + kr->caret.y = y; + kr->caret.h = h; + break; + default: + return -1; + } + return type; } static void @@ -31,9 +57,10 @@ clientthread(void *arg) int fd; Keyreq kr; Keyres res; + Caret caret; uchar out[Ipcmaxresp]; char commit[Maxutf], preedit[Maxutf]; - int n, ncommit, npreedit, want; + int cap, n, ncommit, npreedit, type, want; uchar token; fd = (int)(uintptr)arg; @@ -41,18 +68,34 @@ clientthread(void *arg) reply = chancreate(sizeof(Keyres), 0); kr.reply = reply; kr.owner = &fd; - kr.cap = Cclientpreedit; - while((want = srvreadkey(fd, &kr)) >= 0){ + cap = Cclientpreedit; + memset(&caret, 0, sizeof caret); + kr.cap = cap; + kr.caret = caret; + while((type = srvreadreq(fd, &kr, &want)) >= 0){ + if(type == Ipccap) + cap = want ? Cclientpreedit : 0; + else if(type == Ipccaret) + caret = kr.caret; + kr.cap = cap; + kr.caret = caret; chansend(keyc, &kr); chanrecv(reply, &res); + if(type == Ipccaret) + continue; ncommit = stoutf(&res.commit, commit, sizeof commit); npreedit = stoutf(&res.preedit, preedit, sizeof preedit); - n = ipcpackresp(out, sizeof out, res.eaten, + n = ipcpackresp(out, sizeof out, + type == Ipccap ? 1 : res.eaten, commit, ncommit, preedit, npreedit, want); if(n < 0 || ipcsend(fd, out, n) < 0) break; } kr.op = Keyrelease; + kr.cap = cap; + kr.caret = caret; + kr.ks = 0; + kr.mod = 0; chansend(keyc, &kr); chanrecv(reply, &res); chanfree(reply); diff --git a/tests/ipc_test.c b/tests/ipc_test.c index e6e0e17..23ca2a9 100644 --- a/tests/ipc_test.c +++ b/tests/ipc_test.c @@ -27,6 +27,90 @@ ipc_masks_modifiers(struct ct *t) CT_CHECK(t, ipcreqreset(buf)); } +void +ipc_control_and_caret_frames(struct ct *t) +{ + static const uchar capon[Ipcreqsz] = { + Ipcext|Cclientpreedit, Ipcversion, Ipcopcap, 0, 0, 0, + }; + static const uchar capoff[Ipcreqsz] = { + Ipcext, Ipcversion, Ipcopcap, 0, 0, 0, + }; + static const uchar caret[Ipccaretsz] = { + Ipcext, Ipcversion, Ipcopcaret, 1, + 0xfe, 0xff, 0xff, 0xff, + 0x04, 0x03, 0x02, 0x01, + 0x06, 0x05, 0x00, 0x00, + }; + static const uchar nocaret[Ipccaretsz] = { + Ipcext, Ipcversion, Ipcopcaret, 0, + }; + uchar buf[Ipccaretsz], bad[Ipccaretsz]; + u32int key, mod; + int valid, want; + int32_t x, y, h; + + ipcpackcap(buf, Cclientpreedit); + CT_EQ_MEM(t, capon, buf, sizeof capon); + CT_EQ_INT(t, Ipccap, ipcreqtype(buf)); + /* The probe remains a harmless key-zero request to an old daemon. */ + ipcunpackreq(buf, &want, &mod, &key); + CT_EQ_INT(t, 1, want); + CT_EQ_UINT(t, 0, key); + ipcpackcap(buf, 0); + CT_EQ_MEM(t, capoff, buf, sizeof capoff); + ipcunpackreq(buf, &want, &mod, &key); + CT_EQ_INT(t, 0, want); + CT_EQ_UINT(t, 0, key); + + CT_EQ_INT(t, 0, ipcpackcaret(buf, 1, -2, 0x01020304, 0x506)); + CT_EQ_MEM(t, caret, buf, sizeof caret); + CT_EQ_INT(t, Ipccaret, ipcreqtype(buf)); + if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){ + CT_EQ_INT(t, 1, valid); + CT_EQ_INT(t, -2, x); + CT_EQ_INT(t, 0x01020304, y); + CT_EQ_INT(t, 0x506, h); + } + CT_EQ_INT(t, 0, ipcpackcaret(buf, 1, INT32_MIN, INT32_MAX, 0)); + if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))){ + CT_EQ_INT(t, INT32_MIN, x); + CT_EQ_INT(t, INT32_MAX, y); + CT_EQ_INT(t, 0, h); + } + CT_EQ_INT(t, 0, ipcpackcaret(buf, 0, -2, 3, 4)); + CT_EQ_MEM(t, nocaret, buf, sizeof nocaret); + if(CT_EQ_INT(t, 0, ipcunpackcaret(buf, &valid, &x, &y, &h))) + CT_EQ_INT(t, 0, valid); + CT_EQ_INT(t, -1, ipcpackcaret(buf, 1, 0, 0, -1)); + + memcpy(bad, caret, sizeof bad); + bad[1]++; + CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad)); + CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h)); + memcpy(bad, caret, sizeof bad); + bad[2]++; + CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad)); + CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h)); + memcpy(bad, caret, sizeof bad); + bad[3] = 2; + CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h)); + memcpy(bad, caret, sizeof bad); + bad[0] |= Ipcreqwant; + CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad)); + memcpy(bad, caret, sizeof bad); + bad[15] = 0x80; + CT_EQ_INT(t, -1, ipcunpackcaret(bad, &valid, &x, &y, &h)); + memcpy(bad, capon, sizeof capon); + bad[3] = 1; + CT_EQ_INT(t, Ipcunknown, ipcreqtype(bad)); + + ipcpackreq(buf, 1, 0, 'a'); + CT_EQ_INT(t, Ipclegacy, ipcreqtype(buf)); + ipcpackreset(buf, 1); + CT_EQ_INT(t, Ipclegacy, ipcreqtype(buf)); +} + void ipc_runtime_path(struct ct *t) { diff --git a/tests/server_test.c b/tests/server_test.c index c6a76e1..7e65190 100644 --- a/tests/server_test.c +++ b/tests/server_test.c @@ -142,17 +142,23 @@ sendreset(struct ct *t, Testclient *client, int want) } static Keyreq -nextrequest(struct ct *t, Enginegate *g, int op) +nextrequestcap(struct ct *t, Enginegate *g, int op, int cap) { Keyreq req; memset(&req, 0, sizeof req); chanrecv(g->seen, &req); CT_EQ_INT(t, op, req.op); - CT_EQ_INT(t, Cclientpreedit, req.cap); + CT_EQ_INT(t, cap, req.cap); return req; } +static Keyreq +nextrequest(struct ct *t, Enginegate *g, int op) +{ + return nextrequestcap(t, g, op, Cclientpreedit); +} + static void allowrequest(Enginegate *g) { @@ -175,6 +181,31 @@ readreply(struct ct *t, Testclient *client, int want, char *preedit, int npreedi 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) { @@ -369,3 +400,236 @@ cleanup: while(channbrecv(drawc, &dc) > 0) ; } + +void +server_extension_stream(struct ct *t) +{ + Channel *oldclientc; + Enginegate gate; + Testclient a, b; + Keyreq req; + Drawcmd dc; + uchar frame[Ipccaretsz], token; + char preedit[Maxutf]; + void *aowner, *bowner; + int gateactive; + + memset(&gate, 0, sizeof gate); + memset(&a, 0, sizeof a); + memset(&b, 0, sizeof b); + a.fd = a.peer = b.fd = b.peer = -1; + aowner = bowner = nil; + oldclientc = clientc; + while(channbrecv(drawc, &dc) > 0) + ; + clientc = chancreate(sizeof token, 2); + gate.seen = chancreate(sizeof(Keyreq), 0); + gate.go = chancreate(sizeof token, 0); + gate.stop = chancreate(sizeof token, 0); + gate.done = chancreate(sizeof token, 0); + testengineinit(LangJP); + gateactive = proccreate(enginegate, &gate, 8192) >= 0; + if(!CT_CHECK(t, gateactive)) + goto cleanup; + if(!startclient(t, &a)) + goto cleanup; + + /* Capability and caret frames may be split at any byte boundary. */ + ipcpackcap(frame, Cclientpreedit); + if(!sendframe(t, &a, frame, Ipcreqsz, 1)) + goto cleanup; + req = nextrequest(t, &gate, Keycap); + aowner = req.owner; + CT_EQ_PTR(t, nil, testengineowner()); + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit)); + CT_EQ_STR(t, "", preedit); + + CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, -101, -7, 23)); + if(!sendframe(t, &a, frame, Ipccaretsz, 1)) + goto cleanup; + req = nextrequest(t, &gate, 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(&gate); + checknoreply(t, &a); + + ipcpackreq(frame, 1, 0, 'k'); + if(!sendframe(t, &a, frame, Ipcreqsz, 1)) + goto cleanup; + req = nextrequest(t, &gate, 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(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof 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, &gate, Keycap, 0); + CT_EQ_PTR(t, aowner, req.owner); + CT_EQ_INT(t, 1, req.caret.valid); + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 0, nil, 0)); + + /* 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, &gate, 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(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 0, nil, 0)); + + ipcpackcap(frame, Cclientpreedit); + if(!sendframe(t, &a, frame, Ipcreqsz, 0)) + goto cleanup; + req = nextrequest(t, &gate, Keycap); + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit)); + CT_EQ_STR(t, "", preedit); + ipcpackreq(frame, 1, 0, 'n'); + if(!sendframe(t, &a, frame, Ipcreqsz, 0)) + goto cleanup; + req = nextrequest(t, &gate, Keypress); + CT_EQ_INT(t, 1, req.caret.valid); + CT_EQ_INT(t, -101, req.caret.x); + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &a, 1, preedit, sizeof preedit)); + CT_EQ_STR(t, "ん", preedit); + + disconnectclient(t, &gate, &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, &gate, Keypress); + bowner = req.owner; + CT_EQ_INT(t, 0, req.caret.valid); + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &b, 1, preedit, sizeof preedit)); + CT_EQ_STR(t, "k", preedit); + + disconnectclient(t, &gate, &b, bowner); + +cleanup: + if(a.peer >= 0) + disconnectclient(t, &gate, &a, aowner); + if(b.peer >= 0) + disconnectclient(t, &gate, &b, bowner); + if(gateactive){ + token = 0; + chansend(gate.stop, &token); + chanrecv(gate.done, &token); + } + chanfree(gate.seen); + chanfree(gate.go); + chanfree(gate.stop); + chanfree(gate.done); + chanfree(clientc); + clientc = oldclientc; + while(channbrecv(drawc, &dc) > 0) + ; +} + +void +server_rejects_unknown_extension(struct ct *t) +{ + Channel *oldclientc; + Enginegate gate; + Testclient badver, badop, good; + Keyreq req; + Drawcmd dc; + uchar frame[Ipccaretsz], token; + char preedit[Maxutf]; + void *owner; + int gateactive; + + memset(&gate, 0, sizeof gate); + 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; + oldclientc = clientc; + while(channbrecv(drawc, &dc) > 0) + ; + clientc = chancreate(sizeof token, 3); + gate.seen = chancreate(sizeof(Keyreq), 0); + gate.go = chancreate(sizeof token, 0); + gate.stop = chancreate(sizeof token, 0); + gate.done = chancreate(sizeof token, 0); + testengineinit(LangJP); + gateactive = proccreate(enginegate, &gate, 8192) >= 0; + if(!CT_CHECK(t, gateactive)) + goto cleanup; + + if(!startclient(t, &badver)) + goto cleanup; + CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, 3, 4, 5)); + frame[1]++; + if(!sendframe(t, &badver, frame, sizeof frame, 0)) + goto cleanup; + req = nextrequest(t, &gate, Keyrelease); + allowrequest(&gate); + waitclient(t, &badver); + close(badver.peer); + badver.peer = -1; + + if(!startclient(t, &badop)) + goto cleanup; + CT_EQ_INT(t, 0, ipcpackcaret(frame, 1, 3, 4, 5)); + frame[2]++; + if(!sendframe(t, &badop, frame, sizeof frame, 1)) + goto cleanup; + req = nextrequest(t, &gate, Keyrelease); + allowrequest(&gate); + 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, &gate, Keypress); + owner = req.owner; + allowrequest(&gate); + CT_EQ_INT(t, 1, readreply(t, &good, 1, preedit, sizeof preedit)); + CT_EQ_STR(t, "k", preedit); + disconnectclient(t, &gate, &good, owner); + +cleanup: + if(badver.peer >= 0) + disconnectclient(t, &gate, &badver, nil); + if(badop.peer >= 0) + disconnectclient(t, &gate, &badop, nil); + if(good.peer >= 0) + disconnectclient(t, &gate, &good, owner); + if(gateactive){ + token = 0; + chansend(gate.stop, &token); + chanrecv(gate.done, &token); + } + chanfree(gate.seen); + chanfree(gate.go); + chanfree(gate.stop); + chanfree(gate.done); + chanfree(clientc); + clientc = oldclientc; + while(channbrecv(drawc, &dc) > 0) + ; +} diff --git a/tests/test.h b/tests/test.h index 5dea606..8ca40d2 100644 --- a/tests/test.h +++ b/tests/test.h @@ -66,6 +66,7 @@ void dictionary_candidates(struct ct*); void dictionary_misses_clear_result(struct ct*); void dictionary_emoji_identity(struct ct*); void ipc_masks_modifiers(struct ct*); +void ipc_control_and_caret_frames(struct ct*); void ipc_runtime_path(struct ct*); void ipc_response_pack_boundaries(struct ct*); void ipc_response_empty_and_preedit(struct ct*); @@ -73,6 +74,8 @@ void ipc_response_max_and_drain(struct ct*); void ipc_response_fragmented_and_truncated(struct ct*); void ipc_broken_peer_send(struct ct*); void server_connection_ownership(struct ct*); +void server_extension_stream(struct ct*); +void server_rejects_unknown_extension(struct ct*); void ibus_machine_id_fallback(struct ct*); void ibus_startup_requires_ownership(struct ct*); void ibus_context_lifecycle(struct ct*); diff --git a/tests/unit_test.c b/tests/unit_test.c index 0e3d1ca..5139f0c 100644 --- a/tests/unit_test.c +++ b/tests/unit_test.c @@ -120,6 +120,7 @@ static const struct ct_test tests[] = { { "dict/misses-clear-result", dictionary_misses_clear_result }, { "dict/emoji-identity", dictionary_emoji_identity }, { "ipc/masks-modifiers", ipc_masks_modifiers }, + { "ipc/control-caret-frames", ipc_control_and_caret_frames }, { "ipc/runtime-path", ipc_runtime_path }, { "ipc/response-pack-boundaries", ipc_response_pack_boundaries }, { "ipc/response-empty-preedit", ipc_response_empty_and_preedit }, @@ -127,6 +128,8 @@ static const struct ct_test tests[] = { { "ipc/response-fragmented-truncated", ipc_response_fragmented_and_truncated }, { "ipc/broken-peer-send", ipc_broken_peer_send }, { "server/connection-ownership", server_connection_ownership }, + { "server/extension-stream", server_extension_stream }, + { "server/rejects-unknown-extension", server_rejects_unknown_extension }, { "ibus/machine-id-fallback", ibus_machine_id_fallback }, { "ibus/startup-requires-ownership", ibus_startup_requires_ownership }, { "ibus/context-lifecycle", ibus_context_lifecycle },