Files
strans/tests/ipc_test.c
Hojun-Cho 88ad5f7630 engine: the one capability is a flag
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.
2026-08-17 10:57:15 +09:00

444 lines
12 KiB
C

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
#include "dat.h"
#include "fn.h"
#include "test.h"
#undef accept
#undef listen
#undef send
static int64_t
nowms(void)
{
struct timespec ts;
if(clock_gettime(CLOCK_MONOTONIC, &ts) < 0)
return 0;
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
}
static void ipcclientdeadlines(struct ct*);
static void ipcconnectcloexec(struct ct*);
void
ipc_masks_modifiers(struct ct *t)
{
uchar buf[Ipcreqsz];
u32int key, mod;
int want;
ipcpackreq(buf, 1, Mctrl|Malt|(1<<1)|(1<<4)|(1<<7), 0x1f642);
ipcunpackreq(buf, &want, &mod, &key);
CT_EQ_INT(t, 1, want);
CT_EQ_UINT(t, Mctrl|Malt, mod);
CT_EQ_UINT(t, 0x1f642, key);
CT_CHECK(t, !ipcreqreset(buf));
ipcpackreset(buf, 1);
ipcunpackreq(buf, &want, &mod, &key);
CT_EQ_INT(t, 1, want);
CT_EQ_UINT(t, 0, mod);
CT_EQ_UINT(t, 0, key);
CT_CHECK(t, ipcreqreset(buf));
/* Frontends share one keysym and modifier mapping. */
CT_EQ_UINT(t, 'a', ipckeysym('a', 'a'));
CT_EQ_UINT(t, '1', ipckeysym(0xffb1, '1')); /* KP_1 */
CT_EQ_UINT(t, Kret, ipckeysym(0xff0d, '\r'));
CT_EQ_UINT(t, Kret, ipckeysym(0xff8d, '\r')); /* KP_Enter */
CT_EQ_UINT(t, Ktab, ipckeysym(0xfe20, 0)); /* ISO_Left_Tab */
CT_EQ_UINT(t, Kdown, ipckeysym(0xff99, 0)); /* KP_Down */
CT_EQ_UINT(t, Kback, ipckeysym(0xff08, 8));
CT_EQ_UINT(t, Kspec + 0xff, ipckeysym(0xffff, 0x7f)); /* Delete */
CT_EQ_UINT(t, 0x1f642, ipckeysym(0x101f642, 0x1f642));
CT_EQ_UINT(t, 0, ipckeysym(0xfe03, 0)); /* ISO_Level3_Shift */
CT_EQ_UINT(t, 0, ipckeysym(0xffe1, 0)); /* Shift_L */
CT_EQ_UINT(t, Mshift|Mctrl, ipcmod(Mshift|Mctrl|(1<<1)|(1<<4)));
CT_EQ_UINT(t, Msuper, ipcmod(1<<6));
CT_EQ_UINT(t, Msuper, ipcmod(1<<26));
}
void
ipc_control_and_caret_frames(struct ct *t)
{
static const uchar capon[Ipcreqsz] = {
Ipcext|Ipcreqwant, 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, 1);
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);
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);
}
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);
}
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);
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, Ipckey, ipcreqtype(buf));
ipcpackreset(buf, 1);
CT_EQ_INT(t, Ipckey, ipcreqtype(buf));
}
void
ipc_runtime_path(struct ct *t)
{
char buf[128], fallback[128], *old, *saved;
old = getenv("XDG_RUNTIME_DIR");
saved = old == nil ? nil : strdup(old);
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test", 1);
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
setenv("XDG_RUNTIME_DIR", "/tmp/strans-runtime-test/", 1);
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
CT_EQ_STR(t, "/tmp/strans-runtime-test/strans.sock", buf);
setenv("XDG_RUNTIME_DIR", "relative", 1);
CT_EQ_INT(t, 0, ipcpath(buf, sizeof buf));
snprint(fallback, sizeof fallback, "/tmp/strans.%d", getuid());
CT_EQ_STR(t, fallback, buf);
CT_EQ_INT(t, -1, ipcpath(buf, 4));
if(saved != nil){
setenv("XDG_RUNTIME_DIR", saved, 1);
free(saved);
}else
unsetenv("XDG_RUNTIME_DIR");
}
void
ipc_response_pack_boundaries(struct ct *t)
{
static const uchar withpre[] = { 1, 1, 0, 'A', 2, 0, 'x', 'y' };
static const uchar withoutpre[] = { 1, 1, 0, 'A' };
uchar out[Ipcmaxresp+1], field[Ipcfieldmax];
int n;
n = ipcpackresp(out, sizeof out, 7, "A", 1, "xy", 2, 1);
CT_EQ_INT(t, sizeof withpre, n);
CT_EQ_MEM(t, withpre, out, sizeof withpre);
n = ipcpackresp(out, sizeof out, 1, "A", 1, "xy", 2, 0);
CT_EQ_INT(t, sizeof withoutpre, n);
CT_EQ_MEM(t, withoutpre, out, sizeof withoutpre);
n = ipcpackresp(out, sizeof out, 0, nil, 0, nil, 0, 1);
CT_EQ_INT(t, Ipcresphdrsz + Ipclensz, n);
memset(field, 'x', sizeof field);
memset(out, 0xa5, sizeof out);
n = ipcpackresp(out, Ipcmaxresp, 1,
(char*)field, sizeof field, (char*)field, sizeof field, 1);
CT_EQ_INT(t, Ipcmaxresp, n);
CT_EQ_INT(t, 0, out[1]);
CT_EQ_INT(t, 1, out[2]);
CT_EQ_INT(t, 0, out[3+Ipcfieldmax]);
CT_EQ_INT(t, 1, out[4+Ipcfieldmax]);
CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]);
CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1,
(char*)field, sizeof field, (char*)field, sizeof field, 1));
CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0,
(char*)field, Ipcfieldmax+1, "", 0, 0));
}
void
ipc_response_empty_and_preedit(struct ct *t)
{
uchar first[Ipcmaxresp], second[Ipcmaxresp];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], nfirst, nsecond;
nfirst = ipcpackresp(first, sizeof first, 0, "", 0, "", 0, 0);
nsecond = ipcpackresp(second, sizeof second, 1,
"go", 2, "kana", 4, 1);
if(!CT_CHECK(t, nfirst > 0 && nsecond > 0))
return;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp))){
CT_EQ_INT(t, 0, resp.eaten);
CT_EQ_SIZE(t, 0, resp.commitlen);
CT_EQ_SIZE(t, 0, resp.preeditlen);
CT_EQ_STR(t, "", commit);
}
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
CT_EQ_INT(t, 1, resp.eaten);
CT_EQ_SIZE(t, 2, resp.commitlen);
CT_EQ_SIZE(t, 4, resp.preeditlen);
CT_EQ_STR(t, "go", commit);
CT_EQ_STR(t, "kana", preedit);
}
close(fd[0]);
close(fd[1]);
}
void
ipc_response_max_and_drain(struct ct *t)
{
uchar first[Ipcmaxresp], second[Ipcmaxresp], field[Ipcfieldmax];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], nfirst, nsecond;
memset(field, 'x', sizeof field);
nfirst = ipcpackresp(first, sizeof first, 1,
(char*)field, sizeof field, (char*)field, sizeof field, 1);
nsecond = ipcpackresp(second, sizeof second, 0, "ok", 2, "", 0, 0);
if(!CT_CHECK(t, nfirst == Ipcmaxresp && nsecond > 0))
return;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
if(CT_EQ_INT(t, 0, ipcsend(fd[0], first, nfirst)) &&
CT_EQ_INT(t, 0, ipcsend(fd[0], second, nsecond)) &&
CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit, &resp))){
CT_EQ_SIZE(t, Ipcfieldmax, resp.commitlen);
CT_EQ_SIZE(t, Ipcfieldmax, resp.preeditlen);
CT_EQ_MEM(t, field, commit, Ipcfieldmax);
CT_EQ_MEM(t, field, preedit, Ipcfieldmax);
CT_EQ_INT(t, 0, commit[Ipcfieldmax]);
CT_EQ_INT(t, 0, preedit[Ipcfieldmax]);
}
if(CT_EQ_INT(t, 0, ipcreadresp(fd[1], 0, commit, preedit, &resp)))
CT_EQ_STR(t, "ok", commit);
close(fd[0]);
close(fd[1]);
}
void
ipc_response_fragmented_and_truncated(struct ct *t)
{
uchar frame[Ipcmaxresp];
char commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2], i, n;
n = ipcpackresp(frame, sizeof frame, 1, "abc", 3, "xy", 2, 1);
if(!CT_CHECK(t, n > 0))
return;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
for(i = 0; i < n; i++)
if(ipcsend(fd[0], frame+i, 1) < 0){
CT_ERRORF(t, "fragment send failed");
break;
}
if(i == n && CT_EQ_INT(t, 0, ipcreadresp(fd[1], 1, commit, preedit,
&resp))){
CT_EQ_STR(t, "abc", commit);
CT_EQ_STR(t, "xy", preedit);
}
close(fd[0]);
close(fd[1]);
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n-1));
shutdown(fd[0], SHUT_WR);
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
close(fd[0]);
close(fd[1]);
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
frame[0] = 2;
CT_EQ_INT(t, 0, ipcsend(fd[0], frame, n));
errno = 0;
CT_EQ_INT(t, -1, ipcreadresp(fd[1], 1, commit, preedit, &resp));
CT_EQ_INT(t, EPROTO, errno);
close(fd[0]);
close(fd[1]);
}
void
ipc_broken_peer_send(struct ct *t)
{
int fd[2];
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
close(fd[1]);
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
close(fd[0]);
ipcclientdeadlines(t);
ipcconnectcloexec(t);
}
static void
ipcclientdeadlines(struct ct *t)
{
char fill[4096], commit[Ipcfieldmax+1], preedit[Ipcfieldmax+1];
Ipcresp resp;
int fd[2];
int64_t start, elapsed;
ssize_t n;
if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0){
CT_ERRORF(t, "socketpair failed");
return;
}
start = nowms();
errno = 0;
CT_EQ_INT(t, -1, ipcreadresp(fd[0], 0, commit, preedit, &resp));
elapsed = nowms() - start;
CT_EQ_INT(t, ETIMEDOUT, errno);
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
memset(fill, 'x', sizeof fill);
for(;;){
n = send(fd[0], fill, sizeof fill, MSG_DONTWAIT|MSG_NOSIGNAL);
if(n < 0 && (errno == EAGAIN || errno == EWOULDBLOCK))
break;
if(n <= 0){
CT_ERRORF(t, "fill send failed: %s", strerror(errno));
close(fd[0]);
close(fd[1]);
return;
}
}
start = nowms();
errno = 0;
CT_EQ_INT(t, -1, ipcsend(fd[0], "x", 1));
elapsed = nowms() - start;
CT_EQ_INT(t, ETIMEDOUT, errno);
CT_CHECK(t, elapsed >= 0 && elapsed < 4*Ipcwaitms);
close(fd[0]);
close(fd[1]);
}
static void
ipcconnectcloexec(struct ct *t)
{
struct sockaddr_un addr;
char root[] = "/tmp/strans-ipc.XXXXXX";
char *old, *saved;
int accepted, client, flags, full, listener;
accepted = client = full = listener = -1;
old = getenv("XDG_RUNTIME_DIR");
saved = old == nil ? nil : strdup(old);
if(mkdtemp(root) == nil){
CT_ERRORF(t, "mkdtemp failed: %s", strerror(errno));
free(saved);
return;
}
setenv("XDG_RUNTIME_DIR", root, 1);
memset(&addr, 0, sizeof addr);
addr.sun_family = AF_UNIX;
if(ipcpath(addr.sun_path, sizeof addr.sun_path) < 0 ||
(listener = socket(AF_UNIX, SOCK_STREAM, 0)) < 0 ||
bind(listener, (struct sockaddr*)&addr, sizeof addr) < 0 ||
listen(listener, 0) < 0){
CT_ERRORF(t, "listen failed: %s", strerror(errno));
goto Out;
}
client = ipcconnect();
if(!CT_CHECK(t, client >= 0))
goto Out;
flags = fcntl(client, F_GETFD);
CT_CHECK(t, flags >= 0 && (flags & FD_CLOEXEC) != 0);
errno = 0;
full = ipcconnect();
CT_EQ_INT(t, -1, full);
CT_CHECK(t, errno == EAGAIN || errno == EWOULDBLOCK);
accepted = accept(listener, nil, nil);
if(!CT_CHECK(t, accepted >= 0))
goto Out;
Out:
if(full >= 0)
close(full);
if(accepted >= 0)
close(accepted);
if(client >= 0)
close(client);
if(listener >= 0)
close(listener);
unlink(addr.sun_path);
rmdir(root);
if(saved != nil){
setenv("XDG_RUNTIME_DIR", saved, 1);
free(saved);
}else
unsetenv("XDG_RUNTIME_DIR");
}