/* * test_notify_ipc.c — 宿主测试:任务 #5 SDK↔IPC 桥(meow_notify_ipc.{h,c})。 * * 用本地 AF_UNIX stub socket(回环)验证: * T1 枚举↔字符串映射:CAT_PROGRESS→"progress"、CAT_MEDIA→"media" + media=1; * T2 timeout 映射:-1(ONGOING)→expire=0xFFFFFFFF + ongoing=1;0(NEVER)→expire=0; * >0→该毫秒 + ongoing=0; * T3 回读 MSG_NOTIFY_ACK 拿 launcher 分配的 nid;replaces_id 原样透传;progress 钳位; * T4 无 socket 降级:connect 失败 → meow_notify_ipc_send 返回 0,不崩; * T5 dismiss:meow_notify_ipc_dismiss(nid) 发出 MSG_NOTIFY_DISMISS 帧(负载=nid)。 * * 编译(宿主 gcc,不依赖 meow_ui.a 其余 TU): * gcc -o test_notify_ipc test_notify_ipc.c meow_notify_ipc.c \ * -I. -I../meow_launcherd -lpthread * 运行:./test_notify_ipc (全部 PASS 退出码 0,任一 FAIL 退出码 1) */ #include "meow_notify_ipc.h" #include "meow_ui.h" #include "meow_ipc.h" /* 帧头 / m_notify_t / m_ack_t / MSG_*(同源校验) */ #include #include #include #include #include #include #include /* ---- 捕获:stub 服务端解析到的帧内容(mutex 保护;客户端返回后读取) ---- */ typedef struct { uint32_t type; /* 收到的帧类型 */ m_notify_t n; /* MSG_NOTIFY 负载 */ m_notify_progress_t prog; /* MSG_NOTIFY_PROGRESS 负载 */ uint32_t dismiss_id; /* MSG_NOTIFY_DISMISS 负载 */ uint32_t req_id; uint32_t nid_to_return; /* ACK 中回填的 status */ int got; /* 已收到一帧 */ } cap_t; static cap_t g_cap; static pthread_mutex_t g_cap_mu = PTHREAD_MUTEX_INITIALIZER; static void cap_store(uint32_t type, uint32_t req_id, const uint8_t *pay, uint32_t len, uint32_t nid_to_return) { pthread_mutex_lock(&g_cap_mu); memset(&g_cap, 0, sizeof g_cap); g_cap.type = type; g_cap.req_id = req_id; g_cap.nid_to_return = nid_to_return; if (type == MSG_NOTIFY && len >= sizeof(m_notify_t)) memcpy(&g_cap.n, pay, sizeof(m_notify_t)); if (type == MSG_NOTIFY_PROGRESS && len >= sizeof(m_notify_progress_t)) memcpy(&g_cap.prog, pay, sizeof(m_notify_progress_t)); if (type == MSG_NOTIFY_DISMISS && len >= 4) memcpy(&g_cap.dismiss_id, pay, 4); g_cap.got = 1; pthread_mutex_unlock(&g_cap_mu); } /* ---- stub 服务端线程:accept 一个连接 → 读一帧 → 校验由主线程做,这里只收 + 回 ACK ---- */ static int g_listen_fd = -1; static void *server_thread(void *arg) { (void)arg; int c = accept(g_listen_fd, NULL, NULL); if (c < 0) return NULL; /* 读 16B 头 */ uint8_t hdr[MEOW_IPC_HDR_LEN]; uint32_t off = 0; while (off < sizeof hdr) { ssize_t r = read(c, hdr + off, sizeof hdr - off); if (r <= 0) { close(c); return NULL; } off += (size_t)r; } uint32_t magic, len; uint16_t ver, type; memcpy(&magic, hdr, 4); memcpy(&ver, hdr + 4, 2); memcpy(&type, hdr + 6, 2); memcpy(&len, hdr + 12, 4); uint32_t req_id; memcpy(&req_id, hdr + 8, 4); if (magic != MEOW_IPC_MAGIC || ver != MEOW_IPC_VERSION) { close(c); return NULL; } if (len > MEOW_IPC_MAX) { close(c); return NULL; } uint8_t pay[MEOW_IPC_MAX]; off = 0; while (off < len) { ssize_t r = read(c, pay + off, len - off); if (r <= 0) { close(c); return NULL; } off += (size_t)r; } cap_store(type, req_id, pay, len, (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0)); /* MSG_NOTIFY → 回 m_ack_t(code=OK, status=nid_to_return); * MSG_NOTIFY_DISMISS → 回 m_ack_t(code=OK, status=0,同 launcher 行为)。 */ if (type == MSG_NOTIFY || type == MSG_NOTIFY_DISMISS || type == MSG_NOTIFY_PROGRESS) { m_ack_t a; memset(&a, 0, sizeof a); a.code = MEOW_OK; a.status = (type == MSG_NOTIFY) ? (uint32_t)strtoul(getenv("TEST_NID"), NULL, 0) : 0; uint8_t rhdr[MEOW_IPC_HDR_LEN]; uint32_t rmagic = MEOW_IPC_MAGIC; uint16_t rver = MEOW_IPC_VERSION, rtype = (uint16_t)(type == MSG_NOTIFY ? MSG_NOTIFY_ACK : MSG_ACK); uint32_t rlen = sizeof a; memcpy(rhdr + 0, &rmagic, 4); memcpy(rhdr + 4, &rver, 2); memcpy(rhdr + 6, &rtype, 2); memcpy(rhdr + 8, &req_id, 4); memcpy(rhdr + 12, &rlen, 4); off = 0; while (off < sizeof rhdr) { ssize_t w = write(c, rhdr + off, sizeof rhdr - off); if (w <= 0) break; off += (size_t)w; } off = 0; while (off < rlen) { ssize_t w = write(c, (uint8_t*)&a + off, rlen - off); if (w <= 0) break; off += (size_t)w; } } close(c); return NULL; } static int failures = 0; static void check(const char *name, int cond) { printf("%s %s\n", cond ? "PASS" : "FAIL", name); if (!cond) failures++; } /* 建 listening socket,返回 fd;路径写入 sock_path。 */ static int arb_listen(char *sock_path, size_t slen) { static int count = 0; snprintf(sock_path, slen, "/tmp/meow_notify_ipc_%d_%d.sock", (int)getpid(), count++); unlink(sock_path); int fd = socket(AF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return -1; } struct sockaddr_un a; memset(&a, 0, sizeof a); a.sun_family = AF_UNIX; strncpy(a.sun_path, sock_path, sizeof a.sun_path - 1); if (bind(fd, (struct sockaddr *)&a, sizeof a) < 0) { perror("bind"); close(fd); return -1; } if (listen(fd, 1) < 0) { perror("listen"); close(fd); return -1; } return fd; } int main(void) { setenv("MEOW_LOG_APP", "testapp", 1); /* 固定 app_id,供 stub 校验 */ /* ---------- T1/T2/T3:映射 + ACK 回 nid ---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); if (g_listen_fd < 0) { fprintf(stderr, "cannot create listener\n"); return 2; } setenv("MEOW_IPC_SOCK", sock, 1); setenv("TEST_NID", "0x1234", 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_PROGRESS; nt.urgency = MEOW_NOTIFY_URG_HIGH; nt.title = "下载中"; nt.body = "70%"; nt.icon = "os.meow.hello/icon.png"; nt.timeout_ms = 5000; /* >0 → expire=5000, ongoing=0 */ nt.progress = 150; /* 钳位 → 100 */ uint32_t nid = meow_notify_ipc_send(&nt, 0); pthread_join(th, NULL); close(g_listen_fd); check("T3 send 返回 launcher nid(0x1234)", nid == 0x1234); pthread_mutex_lock(&g_cap_mu); int got = g_cap.got; const m_notify_t *cn = &g_cap.n; pthread_mutex_unlock(&g_cap_mu); check("T3 stub 收到 MSG_NOTIFY 帧", got && g_cap.type == MSG_NOTIFY); check("T1 CAT_PROGRESS→category=progress", !strcmp(cn->category, "progress")); check("T2 timeout>0→expire_timeout=5000", cn->expire_timeout == 5000); check("T2 timeout>0→ongoing=0", cn->ongoing == 0); check("T1 media=0(非媒体)", cn->media == 0); check("T1 progress 钳位 150→100", cn->progress == 100); check("T2 replaces_id 透传=0", cn->replaces_id == 0); check("T1 app_id=testapp", !strcmp(cn->app_id, "testapp")); check("T1 summary=下载中", !strcmp(cn->summary, "下载中")); check("T1 urgency(HIGH)→wire urgency=2", cn->urgency == MEOW_NOTIFY_URG_HIGH); check("T1 icon→app_icon", !strcmp(cn->app_icon, "os.meow.hello/icon.png")); } /* ---------- T1/T2:媒体类型 + ONGOING(-1) ---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); setenv("MEOW_IPC_SOCK", sock, 1); setenv("TEST_NID", "0x9999", 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_MEDIA; nt.urgency = MEOW_NOTIFY_URG_CRITICAL; nt.title = "Now Playing"; nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_ONGOING; /* -1 */ uint32_t nid = meow_notify_ipc_send(&nt, 0); pthread_join(th, NULL); close(g_listen_fd); check("T3 send(media/ongoing) 返回 nid(0x9999)", nid == 0x9999); check("T1 CAT_MEDIA→category=media", !strcmp(g_cap.n.category, "media")); check("T1 CAT_MEDIA→media=1", g_cap.n.media == 1); check("T2 ONGOING(-1)→expire=0xFFFFFFFF", g_cap.n.expire_timeout == 0xFFFFFFFFu); check("T2 ONGOING(-1)→ongoing=1", g_cap.n.ongoing == 1); } /* ---------- T2:NEVER(0) 超时 ---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); setenv("MEOW_IPC_SOCK", sock, 1); setenv("TEST_NID", "0x1", 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_GENERAL; nt.title = "x"; nt.timeout_ms = MEOW_NOTIFY_TIMEOUT_NEVER; /* 0 */ (void)meow_notify_ipc_send(&nt, 0); pthread_join(th, NULL); close(g_listen_fd); check("T2 NEVER(0)→expire=0 & ongoing=0", g_cap.n.expire_timeout == 0 && g_cap.n.ongoing == 0); check("T1 CAT_GENERAL→category=general", !strcmp(g_cap.n.category, "general")); } /* ---------- T3:replaces_id 透传 ---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); setenv("MEOW_IPC_SOCK", sock, 1); setenv("TEST_NID", "0xBEEF", 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_MESSAGE; nt.title = "repl"; nt.timeout_ms = 0; (void)meow_notify_ipc_send(&nt, 0x42u); pthread_join(th, NULL); close(g_listen_fd); check("T3 replaces_id 透传=0x42", g_cap.n.replaces_id == 0x42u); } /* ---------- T4:无 socket 降级不崩 ---------- */ { /* 指向不存在路径 */ char dead[108]; snprintf(dead, sizeof dead, "/tmp/meow_notify_ipc_dead_%d.sock", (int)getpid()); unlink(dead); setenv("MEOW_IPC_SOCK", dead, 1); meow_notify nt; memset(&nt, 0, sizeof nt); nt.cat = MEOW_NOTIFY_CAT_EMAIL; nt.title = "no-socket"; nt.timeout_ms = 0; uint32_t nid = meow_notify_ipc_send(&nt, 0); check("T4 无 socket→返回 0(降级不崩)", nid == 0); meow_notify_ipc_dismiss(0x1234); /* 无 socket 静默,不崩 */ check("T4 dismiss 无 socket 静默不崩", 1); } /* ---------- T5:dismiss 发 MSG_NOTIFY_DISMISS ---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); setenv("MEOW_IPC_SOCK", sock, 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify_ipc_dismiss(0xABCDu); pthread_join(th, NULL); close(g_listen_fd); check("T5 stub 收到 MSG_NOTIFY_DISMISS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_DISMISS); check("T5 dismiss 负载=nid=0xABCD", g_cap.dismiss_id == 0xABCDu); } /* ---------- T6:update_progress 发 MSG_NOTIFY_PROGRESS(nid + progress 钳位)---------- */ { char sock[108]; g_listen_fd = arb_listen(sock, sizeof sock); setenv("MEOW_IPC_SOCK", sock, 1); pthread_t th; pthread_create(&th, NULL, server_thread, NULL); meow_notify_ipc_update_progress(0x1234, 250); /* 钳位 250→100 */ pthread_join(th, NULL); close(g_listen_fd); check("T6 stub 收到 MSG_NOTIFY_PROGRESS 帧", g_cap.got && g_cap.type == MSG_NOTIFY_PROGRESS); check("T6 progress 负载 nid=0x1234", g_cap.prog.nid == 0x1234); check("T6 progress 钳位 250→100", g_cap.prog.progress == 100); } printf("\n%s (%d failure%s)\n", failures ? "RESULT: FAIL" : "RESULT: PASS", failures, failures == 1 ? "" : "s"); return failures ? 1 : 0; }