#define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #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, 3, 1, 0, 'A', 2, 0, 'x', 'y' }; static const uchar withoutpre[] = { 1, 0, 1, 0, 'A' }; uchar out[Ipcmaxresp+1], field[Ipcfieldmax]; int n; n = ipcpackresp(out, sizeof out, 7, 3, "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, 0, "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, 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, 0, (char*)field, sizeof field, (char*)field, sizeof field, 1); CT_EQ_INT(t, Ipcmaxresp, n); CT_EQ_INT(t, 0, out[2]); CT_EQ_INT(t, 1, out[3]); CT_EQ_INT(t, 0, out[4+Ipcfieldmax]); CT_EQ_INT(t, 1, out[5+Ipcfieldmax]); CT_EQ_INT(t, 0xa5, out[Ipcmaxresp]); CT_EQ_INT(t, -1, ipcpackresp(out, Ipcmaxresp-1, 1, 0, (char*)field, sizeof field, (char*)field, sizeof field, 1)); CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 0, (char*)field, Ipcfieldmax+1, "", 0, 0)); /* A take-back is a rune count and never leaves its byte. */ CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, -1, "", 0, "", 0, 0)); CT_EQ_INT(t, -1, ipcpackresp(out, sizeof out, 0, 256, "", 0, "", 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, 0); nsecond = ipcpackresp(second, sizeof second, 1, 0, "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, 0, (char*)field, sizeof field, (char*)field, sizeof field, 1); nsecond = ipcpackresp(second, sizeof second, 0, 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, 2, "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"); }