feat(bt): 蓝牙P1 — netmgrd BT_CTL权威收口(hcitool con判连接/paired-devices补名/a2dp详情/connect-disconnect-pair-unpair-scan)+launcher蓝牙子页sub_page_t重做(8分组,数据全走netmgrd)+设置键audio.output_bt/bt.codec;修act冲突+sub悬垂;宿主82PASS+bt_ctl/bt_page全过

This commit is contained in:
AskaEth committed 2026-09-03 20:01:13 +08:00
1 parent 8c677f127f
commit eb07415f7b
8 files changed
+1178 -90

No files matched your search

+345 -8
View File
@@ -69,6 +69,9 @@ static const char *g_sock = "/usr/data/meow_net.sock"; /* MEOW_NET_S
static const char *g_wpa_cli = "wpa_cli"; /* MEOW_WPA_CLI */
static const char *g_hciconfig = "hciconfig"; /* MEOW_HCICONFIG */
static const char *g_bluetoothctl= "bluetoothctl"; /* MEOW_BLUETOOTHCTL */
static const char *g_hcitool = "hcitool"; /* MEOW_HCITOOL(内核 ACL 连接权威) */
static const char *g_bluealsa_cli= "bluealsa-cli"; /* MEOW_BLUEALSA_CLI(A2DP 传输详情) */
static const char *g_bt_scan_cache = "/tmp/meow_bt_scan.txt"; /* MEOW_BT_SCAN_CACHE(扫描缓存;launcher net.c 同路径) */
static const char *g_ip = "ip"; /* MEOW_IP */
static const char *g_wifi_if = "wlan0"; /* MEOW_WIFI_IF */
@@ -353,6 +356,7 @@ typedef struct {
int powered;
char address[32];
int paired_n; /* -1=null */
int connected_n; /* 已连接(内核 ACL 权威)计数;-1=null */
} bt_t;
typedef struct {
@@ -603,10 +607,12 @@ static void wifi_status(wifi_t *w)
}
/* ---------------- 蓝牙:hciconfig/bluetoothctl 包装 ---------------- */
static int bt_acl_macs(char macs[][24], int max); /* 前向声明(bt_status 早于其定义) */
static void bt_status(bt_t *b)
{
memset(b, 0, sizeof *b);
b->paired_n = -1;
b->connected_n = -1;
char p[192];
snprintf(p, sizeof p, "%s/hci0", g_bt_dir);
if (access(p, F_OK) == 0) b->available = 1;
@@ -670,6 +676,286 @@ static void bt_status(bt_t *b)
}
b->paired_n = n;
}
/* 已连接数:内核 ACL(hcitool con)权威;快速,不受 bluetoothctl 过滤/慢启动影响。 */
{
char macs[8][24];
b->connected_n = bt_acl_macs(macs, 8);
}
}
/* ================================================================
* P1:蓝牙权威收口(BLUETOOTH_FULL_OVERHAUL_PLAN §六/§八 P1)。
* 权威路径设计(修复「已连接设备显示不全」根因):
* - 已连接 = 内核 ACL(hcitool con,快速/权威/不受 bluetoothctl 过滤影响),
* 名/配对来自 bluetoothctl `paired-devices`/`devices`(蓝虫子缓存)——不再依赖
* bluetoothctl `devices Connected`(慢 + 只反映 agent 缓存视图,易截断)。
* - A2DP 详情 = bluealsa-cli a2dp list(codec/采样率/声道/位深/传输状态/角色)。
* - 扫描 = netmgrd 统一生命周期(scan on 才扫、off/超时收尾;缓存 /tmp/meow_bt_scan.txt)。
* 输出均为文本(m_net_cmd_result_t.text),launcher 只做客户端解析,不再 bluetoothctl popen。
* ================================================================ */
static int bt_hex(int c)
{
if (c >= '0' && c <= '9') return c - '0';
if (c >= 'a' && c <= 'f') return c - 'a' + 10;
if (c >= 'A' && c <= 'F') return c - 'A' + 10;
return -1;
}
static int bt_mac_eq(const char *a, const char *b)
{
for (int i = 0; i < 17; i++) {
if (bt_hex((unsigned char)a[i]) != bt_hex((unsigned char)b[i])) return 0;
if (i % 3 == 2 && a[i] != b[i]) return 0;
}
return 1;
}
/* 在行内找第一个形如 XX:XX:XX:XX:XX:XX 的 MAC,返其指针(否则 NULL)。 */
static const char *bt_find_mac(const char *s)
{
while (*s) {
int ok = 1;
for (int i = 0; i < 17; i++) {
int exp_colon = (i % 3 == 2);
if (exp_colon) { if (s[i] != ':') { ok = 0; break; } }
else if (bt_hex((unsigned char)s[i]) < 0) { ok = 0; break; }
}
if (ok) return s;
s++;
}
return NULL;
}
/* 内核 ACL 连接 MAC(hcitool con)。返回数(≤max)。 */
static int bt_acl_macs(char macs[][24], int max)
{
char cmd[160];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s con 2>/dev/null", g_hcitool);
char buf[1024];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) return 0;
int n = 0;
char *line = buf;
while (line && *line && n < max) {
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
const char *m = bt_find_mac(line);
if (m) { memcpy(macs[n], m, 17); macs[n][17] = 0; n++; }
line = nl ? nl + 1 : NULL;
}
return n;
}
/* 设备名解析(bluetoothctl `paired-devices`)+`devices` 两次兜底;命中返 1。 */
static int bt_name_for_mac(const char *mac, char *name, int nlen)
{
if (name && nlen > 0) name[0] = 0;
static const char *subs[] = { "paired-devices", "devices" };
for (int s = 0; s < 2; s++) {
char cmd[176];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s %s 2>/dev/null",
g_bluetoothctl, subs[s]);
char buf[1024];
if (run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd) <= 0) continue;
char *line = buf;
while (line && *line) {
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
char *q = line; while (*q == ' ' || *q == '\t') q++;
if (!strncmp(q, "Device ", 7)) {
q += 7;
char *m = q; while (*m && *m != ' ' && *m != '\t' && *m != '\n' && *m != '\r') m++;
int ml = (int)(m - q);
if (ml == 17 && bt_mac_eq(q, mac)) {
char *nm = m; while (*nm == ' ' || *nm == '\t') nm++;
char *e = nm; while (*e && *e != '\n' && *e != '\r') e++;
*e = 0;
int l = (int)(e - nm); if (l > nlen - 1) l = nlen - 1;
if (l > 0) memcpy(name, nm, (size_t)l), name[l] = 0;
else { memcpy(name, mac, 17); name[17] = 0; }
return 1;
}
}
line = nl ? nl + 1 : NULL;
}
}
return 0;
}
/* 已连接设备文本(权威=内核 ACL;名经 paired/devices 兜底)。行格式 "Device <MAC> <Name>"。 */
static void bt_connected_text(char *buf, int cap)
{
int off = 0;
char macs[8][24];
int n = bt_acl_macs(macs, 8);
for (int i = 0; i < n; i++) {
char name[64] = "";
bt_name_for_mac(macs[i], name, sizeof name);
int l = snprintf(buf + off, (size_t)(cap - off), "Device %s %s\n", macs[i], name);
if (l <= 0 || off + l >= cap) break;
off += l;
}
buf[off] = 0;
}
/* A2DP 传输详情:bluealsa-cli a2dp list → 每端点一行
* "A2DP <mac> codec=<..> rate=<..> channels=<n> bits=<n> acquired=<0|1> role=<src|snk|->"
* 解析宽松:字段缺失留空/0;整串不可辨认时退回原文。 */
static void bt_a2dp_text(char *buf, int cap)
{
char cmd[176];
snprintf(cmd, sizeof cmd, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s a2dp list 2>/dev/null", g_bluealsa_cli);
char raw[MEOW_IPC_MAX - 16];
if (run_cmd(raw, sizeof raw, RUNCMD_TMO_MS, cmd) <= 0) {
snprintf(buf, (size_t)cap, "A2DP none\n");
return;
}
/* 端点累积器 */
char emac[24] = "", role[4] = "-";
char codec[16] = "", rate[16] = "", chn[8] = "", bits[8] = "";
int acquired = -1, has_mac = 0, wrote = 0, off = 0;
char *line = raw;
while (line && *line) {
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
char *p = line; while (*p == ' ' || *p == '\t') p++;
if (*p == '/') { /* 新端点:/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc[:] */
if (has_mac && wrote) {
int l = snprintf(buf + off, (size_t)(cap - off),
"A2DP %s codec=%s rate=%s channels=%s bits=%s acquired=%d role=%s\n",
emac, codec[0] ? codec : "-", rate[0] ? rate : "-",
chn[0] ? chn : "-", bits[0] ? bits : "-",
acquired >= 0 ? acquired : 0, role);
if (l > 0 && off + l < cap) off += l;
}
/* 重置并为新端点解析 MAC/role */
emac[0] = role[0] = codec[0] = rate[0] = chn[0] = bits[0] = 0;
snprintf(role, sizeof role, "%s", "-");
acquired = -1; has_mac = 0; wrote = 0;
const char *dv = strstr(p, "dev_");
if (dv) {
dv += 4;
int j = 0;
const char *q = dv;
while (*q && j < 17) {
if (*q == '_') { if (j == 2 || j == 5 || j == 8 || j == 11 || j == 14) emac[j] = ':'; else break; }
else if (bt_hex((unsigned char)*q) >= 0) emac[j] = *q;
else break;
j++; q++;
}
emac[17] = 0;
has_mac = (j == 17);
if (strstr(p, "a2dpsrc")) snprintf(role, sizeof role, "%s", "src");
else if (strstr(p, "a2dpsnk")) snprintf(role, sizeof role, "%s", "snk");
} else has_mac = 0;
wrote = 1;
line = nl ? nl + 1 : NULL;
continue;
}
/* 字段行:Codec:/Sampling:/Channels:/Bits:/State:/Acquired: */
{ const char *v = strstr(p, "Codec:"); if (v && codec[0] == 0) {
v += 6; while (*v == ' ' || *v == '\t') v++;
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
int l = (int)(e - v); if (l > 15) l = 15; memcpy(codec, v, (size_t)l); codec[l] = 0; } } }
{ const char *v = strstr(p, "Sampling"); if (v && rate[0] == 0) {
v = strchr(v, ':'); if (v) { v++; while (*v == ' ' || *v == '\t') v++;
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
int l = (int)(e - v); if (l > 15) l = 15; memcpy(rate, v, (size_t)l); rate[l] = 0; } } } }
{ const char *v = strstr(p, "Channels:"); if (v && chn[0] == 0) {
v += 9; while (*v == ' ' || *v == '\t') v++;
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
int l = (int)(e - v); if (l > 7) l = 7; memcpy(chn, v, (size_t)l); chn[l] = 0; } } }
{ const char *v = strstr(p, "Bits"); if (v && bits[0] == 0) {
v = strchr(v, ':'); if (v) { v++; while (*v == ' ' || *v == '\t') v++;
{ const char *e = v; while (*e && *e != '\n' && *e != '\r') e++;
while (e > v && (e[-1] == ' ' || e[-1] == '\t')) e--;
int l = (int)(e - v); if (l > 7) l = 7; memcpy(bits, v, (size_t)l); bits[l] = 0; } } } }
if (strstr(p, "State:") || strstr(p, "State :") || strstr(p, "Acquired")) {
if (strstr(p, "RUNNING") || strstr(p, "ACTIVE") || strstr(p, "acquired") || strstr(p, "yes"))
acquired = 1;
}
line = nl ? nl + 1 : NULL;
}
/* 尾端点收尾 */
if (has_mac && wrote) {
int l = snprintf(buf + off, (size_t)(cap - off),
"A2DP %s codec=%s rate=%s channels=%s bits=%s acquired=%d role=%s\n",
emac, codec[0] ? codec : "-", rate[0] ? rate : "-",
chn[0] ? chn : "-", bits[0] ? bits : "-",
acquired >= 0 ? acquired : 0, role);
if (l > 0 && off + l < cap) off += l;
}
if (off == 0) snprintf(buf, (size_t)cap, "A2DP none\n");
else buf[off] = 0;
}
/* A2DP 摘要(首个带 codec/rate 的端点)给 overview JSON;失败置空。 */
static void bt_a2dp_summary(char *codec, int clen, char *rate, int rlen)
{
if (codec && clen > 0) codec[0] = 0;
if (rate && rlen > 0) rate[0] = 0;
char buf[MEOW_IPC_MAX - 16];
bt_a2dp_text(buf, sizeof buf);
char *line = buf;
while (line && *line) {
char *nl = strchr(line, '\n'); if (nl) *nl = 0;
if (!strncmp(line, "A2DP ", 5)) {
const char *c = strstr(line, " codec=");
const char *r = strstr(line, " rate=");
if (c && codec) { c += 7; const char *e = c; while (*e && *e != ' ' && *e != '\n') e++;
int l = (int)(e - c); if (l > clen - 1) l = clen - 1; memcpy(codec, c, (size_t)l); codec[l] = 0; }
if (r && rate) { r += 6; const char *e = r; while (*e && *e != ' ' && *e != '\n') e++;
int l = (int)(e - r); if (l > rlen - 1) l = rlen - 1; memcpy(rate, r, (size_t)l); rate[l] = 0; }
if ((codec && codec[0]) || (rate && rate[0])) return;
}
line = nl ? nl + 1 : NULL;
}
}
/* 附近设备文本:netmgrd 权威扫描缓存(/tmp/meow_bt_scan.txt),行 "Device <MAC> <Name>"(去重)。 */
static void bt_devices_text(char *buf, int cap)
{
int off = 0;
FILE *f = fopen(g_bt_scan_cache, "r");
if (!f) { buf[0] = 0; return; }
char seen[16][24]; int n_seen = 0;
char line[256];
while (fgets(line, sizeof line, f) && n_seen < 16) {
char *p = line; while (*p == ' ' || *p == '\t') p++;
const char *dv = NULL;
if (!strncmp(p, "Device ", 7)) dv = p + 7;
else if (!strncmp(p, "[NEW] Device ", 13)) dv = p + 13;
if (!dv) continue;
const char *mac = bt_find_mac(dv);
if (!mac) continue;
int dup = 0;
for (int i = 0; i < n_seen; i++) if (bt_mac_eq(seen[i], mac)) { dup = 1; break; }
if (dup) continue;
memcpy(seen[n_seen], mac, 17); seen[n_seen][17] = 0; n_seen++;
char *nm = (char *)(mac + 17); while (*nm == ' ' || *nm == '\t') nm++;
char *e = nm; while (*e && *e != '\n' && *e != '\r') e++;
*e = 0;
int l = snprintf(buf + off, (size_t)(cap - off), "Device %s %s\n", mac, nm);
if (l <= 0 || off + l >= cap) break;
off += l;
}
fclose(f);
buf[off] = 0;
}
/* 扫描生命周期(§七:进「附近查找」才 scan on;退页/超时 off)。默认 7s 节流。 */
static long g_bt_scan_ms = 0;
static void bt_scan_on(void)
{
long t = mono_now();
if (g_bt_scan_ms && t - g_bt_scan_ms < 7000) return;
g_bt_scan_ms = t;
char cmd[256];
snprintf(cmd, sizeof cmd,
"(sleep 1; echo 'scan on'; sleep 4; echo 'scan off'; echo 'devices'; echo 'quit') | "
"bluetoothctl > '%s' 2>&1 &", g_bt_scan_cache);
system(cmd);
}
static void bt_scan_off(void)
{
system("PATH=/bin:/sbin:/usr/bin:/usr/sbin bluetoothctl scan off 2>/dev/null");
}
/* ---------------- rfkill:统一软开关 ---------------- */
@@ -974,6 +1260,11 @@ static void build_status_json(char *out, size_t cap)
bt.powered ? "true" : "false");
jadd_str_or_null(&w, "address", bt.address);
jadd_int_or_null(&w, "paired", bt.paired_n);
jadd_int_or_null(&w, "connected", bt.connected_n);
{ char ac[16] = "", ar[16] = "";
bt_a2dp_summary(ac, sizeof ac, ar, sizeof ar);
jadd_str_or_null(&w, "a2dp_codec", ac);
jadd_str_or_null(&w, "a2dp_rate", ar); }
jadd(&w, "\"dns\":[");
{
char ns[DNS_MAX][40];
@@ -2326,8 +2617,9 @@ static int wifi_cmd_is_write(const char *cmd, const char *arg)
}
static int bt_cmd_is_write(const char *cmd)
{
if (!strcmp(cmd, "status") || !strcmp(cmd, "paired")) return 0;
return 1; /* on/off */
if (!strcmp(cmd, "status") || !strcmp(cmd, "paired") ||
!strcmp(cmd, "connected") || !strcmp(cmd, "a2dp") || !strcmp(cmd, "devices")) return 0;
return 1; /* on/off/connect/disconnect/pair/unpair/scan */
}
/* 写级授权决策(纯函数,宿主自测用;handle_conn 复用):读级任意;写级仅系统身份 + MeowOS 模式。
@@ -2597,14 +2889,13 @@ static void bt_onoff(const char *cmd, ctl_result_t *r)
static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
{
(void)arg;
ctl_reset(r, MEOW_OK);
if (!strcmp(cmd, "status")) {
bt_t b;
bt_status(&b);
char msg[192];
snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d",
b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n);
snprintf(msg, sizeof msg, "bt available=%d powered=%d address=%s paired=%d connected=%d",
b.available, b.powered, b.address[0] ? b.address : "-", b.paired_n, b.connected_n);
ctl_sets(r, MEOW_OK, msg);
} else if (!strcmp(cmd, "on") || !strcmp(cmd, "off")) {
bt_onoff(cmd, r);
@@ -2615,6 +2906,33 @@ static void bt_ctl_execute(const char *cmd, const char *arg, ctl_result_t *r)
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
if (n <= 0) ctl_sets(r, MEOW_ERR_NODEV, "bt paired: 无输出 (bluetoothctl 不可用/未开)");
else ctl_sets(r, MEOW_OK, buf);
} else if (!strcmp(cmd, "connected")) {
char buf[MEOW_IPC_MAX - 16];
bt_connected_text(buf, sizeof buf);
ctl_sets(r, MEOW_OK, buf);
} else if (!strcmp(cmd, "a2dp")) {
char buf[MEOW_IPC_MAX - 16];
bt_a2dp_text(buf, sizeof buf);
ctl_sets(r, MEOW_OK, buf);
} else if (!strcmp(cmd, "devices")) {
char buf[MEOW_IPC_MAX - 16];
bt_devices_text(buf, sizeof buf);
ctl_sets(r, MEOW_OK, buf);
} else if (!strcmp(cmd, "connect") || !strcmp(cmd, "disconnect") ||
!strcmp(cmd, "pair") || !strcmp(cmd, "unpair")) {
if (!arg || !arg[0]) { char m[48]; snprintf(m, sizeof m, "bt %s: 需 <mac>", cmd); ctl_sets(r, MEOW_ERR_INVAL, m); return; }
const char *sub = (!strcmp(cmd, "unpair")) ? "remove" : cmd; /* bluetoothctl 取消配对=remove */
char cmd2[192];
snprintf(cmd2, sizeof cmd2, "PATH=/bin:/sbin:/usr/bin:/usr/sbin %s %s %s 2>/dev/null",
g_bluetoothctl, sub, arg);
char buf[256];
int n = run_cmd(buf, sizeof buf, RUNCMD_TMO_MS, cmd2);
if (n >= 0) ctl_sets(r, MEOW_OK, buf[0] ? buf : "bt cmd: 已下发");
else ctl_sets(r, MEOW_ERR_NODEV, "bt cmd: 后端执行失败");
} else if (!strcmp(cmd, "scan")) {
if (!arg || !strcmp(arg, "on")) { bt_scan_on(); ctl_sets(r, MEOW_OK, "bt scan on: 后台节流扫描已触发"); }
else if (!strcmp(arg, "off")) { bt_scan_off(); ctl_sets(r, MEOW_OK, "bt scan off: 扫描已停"); }
else ctl_sets(r, MEOW_ERR_INVAL, "bt scan: 用法 on|off");
} else {
ctl_sets(r, MEOW_ERR_INVAL, "bt: 未知子命令");
}
@@ -3204,9 +3522,15 @@ static int cli_status(void)
int bpow = strstr(obj, "\"powered\":true") != NULL;
jstr(obj, "address", baddr, sizeof baddr);
int bpaired = -1;
int bconn = -1;
jint(obj, "paired", &bpaired);
printf("bt available=%d powered=%d address=%s paired=%d\n",
bavail, bpow, baddr[0] ? baddr : "-", bpaired);
jint(obj, "connected", &bconn);
char bcodec[24] = "", brate[24] = "";
jstr(obj, "a2dp_codec", bcodec, sizeof bcodec);
jstr(obj, "a2dp_rate", brate, sizeof brate);
printf("bt available=%d powered=%d address=%s paired=%d connected=%d a2dp=%s@%s\n",
bavail, bpow, baddr[0] ? baddr : "-", bpaired, bconn,
bcodec[0] ? bcodec : "-", brate[0] ? brate : "-");
}
/* rfkill 数组 */
{
@@ -3381,7 +3705,8 @@ static int cli_policy(int op, const char *entity, const char *arg)
static const char *usage(void)
{
return "status | iface [name] | wifi scan|networks|connect <ssid>[,psk]|disconnect|on|off"
" | bt status|on|off|paired | rfkill [<type> <on|off>] | udhcpc renew|release [if]"
" | bt status|on|off|paired|connected|a2dp|devices|scan on|off|connect|disconnect|pair|unpair <mac>"
" | rfkill [<type> <on|off>] | udhcpc renew|release [if]"
" | apps | conns [entity] | top | dns status|stats|flush"
" | policy show [entity] | policy set <entity> allow|block | policy domain <entity> +host|-host | policy del <entity>"
" | help";
@@ -3444,6 +3769,15 @@ static int cli_run(int n, char **v)
}
if (!strcmp(cmd, "bt")) {
if (!arg) { fprintf(stderr, "bt 子命令缺失(%s)\n", usage()); return 1; }
if (!strcmp(arg, "connect") || !strcmp(arg, "disconnect") ||
!strcmp(arg, "pair") || !strcmp(arg, "unpair")) {
if (!arg2) { fprintf(stderr, "bt %s 需 <mac>\n", arg); return 1; }
return cli_cmd_result(MSG_NET_BT_CTL, arg, arg2);
}
if (!strcmp(arg, "scan")) {
if (arg2 && !strcmp(arg2, "off")) return cli_cmd_result(MSG_NET_BT_CTL, "scan", "off");
return cli_cmd_result(MSG_NET_BT_CTL, "scan", "on");
}
return cli_cmd_result(MSG_NET_BT_CTL, arg, NULL);
}
if (!strcmp(cmd, "rfkill")) {
@@ -3480,6 +3814,9 @@ int main(int argc, char **argv)
if ((e = getenv("MEOW_WPA_CLI")) && e[0]) g_wpa_cli = e;
if ((e = getenv("MEOW_HCICONFIG")) && e[0]) g_hciconfig = e;
if ((e = getenv("MEOW_BLUETOOTHCTL")) && e[0]) g_bluetoothctl = e;
if ((e = getenv("MEOW_HCITOOL")) && e[0]) g_hcitool = e;
if ((e = getenv("MEOW_BLUEALSA_CLI")) && e[0]) g_bluealsa_cli = e;
if ((e = getenv("MEOW_BT_SCAN_CACHE")) && e[0]) g_bt_scan_cache = e;
if ((e = getenv("MEOW_IP")) && e[0]) g_ip = e;
if ((e = getenv("MEOW_WIFI_IF")) && e[0]) g_wifi_if = e;
if ((e = getenv("MEOW_NET_SOCK")) && e[0]) g_sock = e;
+136
View File
@@ -0,0 +1,136 @@
/* test_bt_ctl.c — meow_netmgrd 蓝牙 P1 权威收口 纯逻辑宿主自测(本程序非生产代码)。
*
* 通过 #include 生产源文件(meow_netmgrd.c,用 #define main 改名避免入口冲突),同一 TU 直接访问
* FILE-STATIC 函数/全局:bt_cmd_is_write / bt_write_authorize / bt_ctl_execute(connect/disconnect/
* pair/unpair/connected/a2dp/devices/status)。工具经桩脚本(g_bluetoothctl/g_hcitool/g_bluealsa_cli/
* g_bt_scan_cache 指向 $T/桩)覆盖,不依赖真实蓝牙栈。
*
* 用法:cc -Wall -Wextra -I../meow_launcherd test_bt_ctl.c -o test_bt_ctl && ./test_bt_ctl
*/
#define main meow_netmgrd_prod_main_unused
#include "meow_netmgrd.c"
#undef main
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
static int g_fail = 0;
#define CHECK(cond) do { if (!(cond)) { printf(" FAIL: %s (line %d)\n", #cond, __LINE__); g_fail++; } \
else { printf(" ok : %s\n", #cond); } } while (0)
static void wf(const char *p, const char *s)
{
FILE *f = fopen(p, "w");
if (f) { fputs(s, f); fclose(f); }
}
int main(void)
{
static const char *T = "/tmp/nmg_bt";
char p[192];
mkdir(T, 0755);
/* 假 /sys/class/bluetooth/hci0(available/powered/address) */
{ snprintf(p, sizeof p, "%s/bt", T); mkdir(p, 0755);
snprintf(p, sizeof p, "%s/bt/hci0", T); mkdir(p, 0755);
snprintf(p, sizeof p, "%s/bt/hci0/address", T); wf(p, "00:1A:7D:DA:71:13\n");
snprintf(p, sizeof p, "%s/bt/hci0/power", T); wf(p, "on\n"); }
/* 桩:bluetoothctl(paired/devices/connect/remove)、hcitool con、bluealsa-cli a2dp、扫描缓存 */
snprintf(p, sizeof p, "%s/bluetoothctl", T); wf(p,
"#!/bin/sh\ncase \"$1\" in\n"
" paired|paired-devices|devices) echo 'Device 33:DE:2E:21:AD:30 Pudding'; echo 'Device 11:22:33:44:55:66 Speaker';;\n"
" connect|disconnect|pair|remove) echo 'Controller 00:1A:7D:DA:71:13'; echo 'Device 33:DE:2E:21:AD:30 Connected: yes';;\n"
"esac\nexit 0\n"); chmod(p, 0755);
snprintf(p, sizeof p, "%s/hcitool", T); wf(p,
"#!/bin/sh\necho '\t< ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER'\n"
"echo '\t< ACL 11:22:33:44:55:66 handle 13 state 1 lm SLAVE'\nexit 0\n"); chmod(p, 0755);
snprintf(p, sizeof p, "%s/bluealsa-cli", T); wf(p,
"#!/bin/sh\nif [ \"${1:-}\" = \"a2dp\" ]; then\n"
"echo '/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc:'\n"
"echo '\tCodec: SBC'\necho '\tSampling: 44100 Hz'\necho '\tChannels: 2'\n"
"echo '\tBits per Sample: 16'\necho '\tState: RUNNING'\necho '\tAcquired: yes'\nfi\nexit 0\n"); chmod(p, 0755);
snprintf(p, sizeof p, "%s/bt_scan.txt", T); wf(p,
"[NEW] Device 33:DE:2E:21:AD:30 Pudding\n[NEW] Device AA:BB:CC:DD:EE:FF Earbuds\n");
g_bluetoothctl = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluetoothctl", T); static char s[160];
snprintf(s, sizeof s, "%s", x); g_bluetoothctl = s; }
g_hcitool = ""; { char x[160]; snprintf(x, sizeof x, "%s/hcitool", T); static char s[160];
snprintf(s, sizeof s, "%s", x); g_hcitool = s; }
g_bluealsa_cli = ""; { char x[160]; snprintf(x, sizeof x, "%s/bluealsa-cli", T); static char s[160];
snprintf(s, sizeof s, "%s", x); g_bluealsa_cli = s; }
g_bt_scan_cache = ""; { char x[160]; snprintf(x, sizeof x, "%s/bt_scan.txt", T); static char s[160];
snprintf(s, sizeof s, "%s", x); g_bt_scan_cache = s; }
g_bt_dir[0] = 0; snprintf(g_bt_dir, sizeof g_bt_dir, "%s/bt", T);
ctl_result_t r;
printf("== bt_cmd_is_write 分类(读/写级) ==\n");
CHECK(bt_cmd_is_write("status") == 0);
CHECK(bt_cmd_is_write("paired") == 0);
CHECK(bt_cmd_is_write("connected") == 0);
CHECK(bt_cmd_is_write("a2dp") == 0);
CHECK(bt_cmd_is_write("devices") == 0);
CHECK(bt_cmd_is_write("on") == 1);
CHECK(bt_cmd_is_write("off") == 1);
CHECK(bt_cmd_is_write("connect") == 1);
CHECK(bt_cmd_is_write("disconnect") == 1);
CHECK(bt_cmd_is_write("pair") == 1);
CHECK(bt_cmd_is_write("unpair") == 1);
CHECK(bt_cmd_is_write("scan") == 1);
printf("== bt_ctl_execute 写级(connect/disconnect/pair/unpair,经桩 bluetoothctl) ==\n");
bt_ctl_execute("connect", "33:DE:2E:21:AD:30", &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "Connected") != NULL);
bt_ctl_execute("disconnect", "33:DE:2E:21:AD:30", &r);
CHECK(r.code == MEOW_OK);
bt_ctl_execute("pair", "33:DE:2E:21:AD:30", &r);
CHECK(r.code == MEOW_OK);
bt_ctl_execute("unpair", "33:DE:2E:21:AD:30", &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "Connected") != NULL); /* remove 桩回 Connected: yes */
bt_ctl_execute("connect", "", &r);
CHECK(r.code == MEOW_ERR_INVAL); /* 缺 mac → INVAL */
bt_ctl_execute("scan", NULL, &r);
CHECK(r.code == MEOW_OK); /* scan on(默认) */
CHECK(strstr(r.text, "scan on") != NULL);
bt_ctl_execute("scan", "off", &r);
CHECK(r.code == MEOW_OK);
printf("== bt_ctl_execute 读级(connected/a2dp/devices/status,权威) ==\n");
bt_ctl_execute("connected", NULL, &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "Device 33:DE:2E:21:AD:30") != NULL);
CHECK(strstr(r.text, "Pudding") != NULL);
bt_ctl_execute("a2dp", NULL, &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "codec=SBC") != NULL);
CHECK(strstr(r.text, "rate=44100") != NULL);
CHECK(strstr(r.text, "acquired=1") != NULL);
CHECK(strstr(r.text, "role=src") != NULL);
snprintf(p, sizeof p, "%s/bt_scan.txt", T); wf(p, /* 重写(bt scan on 曾覆盖) */
"[NEW] Device 33:DE:2E:21:AD:30 Pudding\n[NEW] Device AA:BB:CC:DD:EE:FF Earbuds\n");
bt_ctl_execute("devices", NULL, &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "Earbuds") != NULL);
CHECK(strstr(r.text, "Device 33:DE:2E:21:AD:30") != NULL);
bt_ctl_execute("status", NULL, &r);
CHECK(r.code == MEOW_OK);
CHECK(strstr(r.text, "connected=2") != NULL);
printf("== bt_write_authorize(系统身份 + MeowOS 才放行写级) ==\n");
caller_t other; memset(&other, 0, sizeof other); other.kind = CALL_OTHER;
caller_t sys; memset(&sys, 0, sizeof sys); sys.kind = CALL_SYS;
snprintf(sys.pkg, sizeof sys.pkg, "meow_net");
CHECK(bt_write_authorize(&other, 1, "connect") == MEOW_ERR_PERM); /* 非系统 → 拒 */
CHECK(bt_write_authorize(&sys, 1, "connect") == MEOW_OK); /* 系统+meow → 放 */
CHECK(bt_write_authorize(&sys, 0, "connect") == MEOW_ERR_PERM); /* 系统+hiby → 拒 */
CHECK(bt_write_authorize(&other, 1, "status") == MEOW_OK); /* 读级任意 → 放 */
if (g_fail) { printf("\nBT_CTL TEST: %d FAIL\n", g_fail); return 1; }
printf("\nBT_CTL TEST: ALL PASS\n");
return 0;
}
+65 -3
View File
@@ -75,8 +75,39 @@ echo " UP RUNNING PSCAN"
EOF
cat > "$T/bluetoothctl" <<'EOF'
#!/bin/sh
echo "Device 00:1A:7D:DA:71:13 My Headset"
echo "Device 11:22:33:44:55:66 Speaker"
case "${1:-}" in
paired|paired-devices)
echo "Device 00:1A:7D:DA:71:13 My Headset"
echo "Device 11:22:33:44:55:66 Speaker";;
devices)
echo "Device 00:1A:7D:DA:71:13 My Headset"
echo "Device 11:22:33:44:55:66 Speaker"
echo "Device 33:DE:2E:21:AD:30 Pudding";;
connect|disconnect|pair|remove)
echo "Controller 00:1A:7D:DA:71:13 Connected: yes";;
*) echo "Device 00:1A:7D:DA:71:13 My Headset"; echo "Device 11:22:33:44:55:66 Speaker";;
esac
exit 0
EOF
cat > "$T/hcitool" <<'EOF'
#!/bin/sh
echo " < ACL 33:DE:2E:21:AD:30 handle 12 state 1 lm MASTER"
echo " < ACL 11:22:33:44:55:66 handle 13 state 1 lm SLAVE"
exit 0
EOF
cat > "$T/bluealsa-cli" <<'EOF'
#!/bin/sh
if [ "${1:-}" = "a2dp" ]; then
echo "/org/bluealsa/hci0/dev_33_DE_2E_21_AD_30/a2dpsrc:"
echo " Type: A2DP Source"
echo " Codec: SBC"
echo " Sampling: 44100 Hz"
echo " Channels: 2"
echo " Bits per Sample: 16"
echo " State: RUNNING"
echo " Acquired: yes"
fi
exit 0
EOF
cat > "$T/ip" <<'EOF'
#!/bin/sh
@@ -86,13 +117,14 @@ for a in "$@"; do
done
exit 0
EOF
for f in wpa_cli hciconfig bluetoothctl ip; do chmod +x "$T/$f"; done
for f in wpa_cli hciconfig bluetoothctl hcitool bluealsa-cli ip; do chmod +x "$T/$f"; done
mkdir -p "$T/logs" "$T/pid"
# 期2:DNS 禁用(宿主无 root 不绑 53),观测层经 MEOW_TCP/UDP 打桩;amsd/svcd 缺省(实体归因走 proc:comm→unknown)。
ENVS="MEOW_NET_DIR=$NET MEOW_NETDEV=$T/netdev MEOW_NET_ROUTE=$T/route MEOW_RESOLV=$T/resolv
MEOW_RFKILL_DIR=$RF MEOW_BT_DIR=$BT MEOW_BOOT_MODE=$T/boot_mode MEOW_BOOTTS_CACHE=$T/bootts
MEOW_IP=$T/ip MEOW_WPA_CLI=$T/wpa_cli MEOW_HCICONFIG=$T/hciconfig MEOW_BLUETOOTHCTL=$T/bluetoothctl
MEOW_HCITOOL=$T/hcitool MEOW_BLUEALSA_CLI=$T/bluealsa-cli MEOW_BT_SCAN_CACHE=$T/bt_scan.txt
MEOW_NET_SOCK=$T/net.sock MEOW_NET_POLL_SEC=1 MEOW_LOG_DIR=$T/logs
MEOW_TCP=$T/tcp MEOW_UDP=$T/udp MEOW_NET_POLICY=$T/policy
MEOW_AMS_SOCK=$T/no_ams MEOW_SVCD_SOCK=$T/no_svcd MEOW_DNS_DISABLE=1"
@@ -137,10 +169,40 @@ ck T4a "permission denied" "$OUT"
OUT=$(env $ENVS $Q "$T/meow_net" bt status 2>&1)
ck T5a "available=1 powered=1" "$OUT"
ck T5b "paired=2" "$OUT"
ck T5b2 "connected=2" "$OUT" # P1:内核 ACL 权威已连接数
OUT=$(env $ENVS $Q "$T/meow_net" bt paired 2>&1)
ck T5c "My Headset" "$OUT"
ck T5d "Speaker" "$OUT"
# ---- T5e: bt connected(P1:内核 ACL 权威,名经 devices 缓存兜底)----
OUT=$(env $ENVS $Q "$T/meow_net" bt connected 2>&1)
ck T5e "Device 33:DE:2E:21:AD:30 Pudding" "$OUT"
ck T5e2 "Device 11:22:33:44:55:66 Speaker" "$OUT"
# ---- T5f: bt a2dp(P1:bluealsa-cli a2dp list → codec/rate/acquired/role)----
OUT=$(env $ENVS $Q "$T/meow_net" bt a2dp 2>&1)
ck T5f "codec=SBC" "$OUT"
ck T5f2 "rate=44100" "$OUT"
ck T5f3 "acquired=1" "$OUT"
ck T5f4 "role=src" "$OUT"
# ---- T5g: bt devices(P1:netmgrd 权威扫描缓存)----
cat > "$T/bt_scan.txt" <<'EOF'
[NEW] Device 33:DE:2E:21:AD:30 Pudding
[NEW] Device AA:BB:CC:DD:EE:FF Earbuds
[DEL] Device 00:11:22:33:44:55
EOF
OUT=$(env $ENVS $Q "$T/meow_net" bt devices 2>&1)
ck T5g "Device 33:DE:2E:21:AD:30 Pudding" "$OUT"
ck T5g2 "Device AA:BB:CC:DD:EE:FF Earbuds" "$OUT"
# ---- T5h: bt 写级(connect/pair 等)在宿主 CLI=CALL_OTHER → 拒(仅系统身份+MeowOS 放行,见 T6)----
OUT=$(env $ENVS $Q "$T/meow_net" bt connect 33:DE:2E:21:AD:30 2>&1); RC=$?
ck T5h "permission denied" "$OUT"
ck T5h2 "rc=1" "rc=$RC"
OUT=$(env $ENVS $Q "$T/meow_net" bt scan on 2>&1)
ck T5i "permission denied" "$OUT"
# ---- T6: HibyOS 只读降级(读可查,写拒)----
echo hiby > "$T/boot_mode"
sleep 2 # 等守护下一拍热切换