主题
xhim_v1 C ABI Reference
普通 iOS、Android、Windows、HarmonyOS App 不需要使用本页。 本页面向 XHIM 平台 Wrapper 维护者和自研语言 Bridge。
唯一稳定的跨语言二进制边界是 ffi/include/xhim/xhim_v1.h。C++ 类布局、 STL 类型和内部 Engine 接口不属于兼容合同。
1. 版本和能力
xhim_v1_abi_versionxhim_v1_version_stringxhim_v1_source_commitxhim_v1_client_get_compatibilityxhim_v1_client_get_device_session_policyxhim_v1_client_get_diagnostics
Wrapper 初始化时必须核对 ABI version。发行包还应核对 wrapper、native library 和 manifest 的版本/commit 一致性。
1.1 Application Extension 能力协商
认证成功后,xhim_v1_client_get_compatibility 返回本次会话协商完成的 server_capabilities。业务扩展使用以下五个稳定 capability;名称区分 字段所属 family,不能互相替代:
| Capability | 受保护的扩展数据 |
|---|---|
conversation.application_extension | 会话偏好及会话分页扩展 |
conversation.message_retention | 账号私有的会话消息滚动保留策略 |
social.group_application_extension | 群资料、群成员和群治理扩展 |
social.relationship_application_extension | 好友属性和方向性黑名单扩展 |
social.request_application_extension | 好友申请和入群申请扩展 |
user.profile_application_extension | 用户资料扩展 |
显式读写扩展的专用入口必须先检查对应 capability。新 Core 连接未宣告该 能力的旧 Server 时,这些入口同步返回 XHIM_V1_STATUS_UNSUPPORTED,不会 创建异步 request、调用 backend、读取本地扩展投影或产生任何 mutation。 调用方不得将 UNSUPPORTED 降级成旧入口后再把扩展伪装成写入成功。
不使用扩展的 legacy API 不受这些 capability 影响,继续按旧合同工作。 分页 accessor 本身没有 Client/compatibility 上下文,因此不会自行协商: 严格 Wrapper 先检查 capability,再调用 legacy list/search/read 与 accessor。 平行扩展数组 count == 0 表示旧 Core 或未投影,属于“不可用”; count == 主数组数量 表示可用,此时每个空 byte view 才是权威空值。 Accessor 对这两种形态都返回 OK,其他数量或指针组合仍返回 INVALID_ARGUMENT。
2. Client 生命周期
xhim_v1_client_createxhim_v1_client_startxhim_v1_client_loginxhim_v1_client_update_credentialxhim_v1_client_logoutxhim_v1_client_get_statexhim_v1_client_get_session_identityxhim_v1_client_get_app_runtime_statexhim_v1_client_set_app_runtime_statexhim_v1_client_notify_network_availablexhim_v1_client_shutdownxhim_v1_client_destroy
shutdown 是异步收尾,destroy 是最终释放。外部必须把 destroy 与所有 Client API 串行化;destroy 开始后不得再提交请求。
2.1 App Runtime State(设备投递状态)
App Runtime State 是当前 Client 的 OS 生命周期事实,只有两个强类型值: XHIM_V1_APP_RUNTIME_FOREGROUND 和 XHIM_V1_APP_RUNTIME_BACKGROUND。它与用户可见的 Presence online/away 完全分离:Presence 用于展示和 TTL 过期,Runtime State 只供当前 device session 的 Push、实时路由、重连和 Sync 策略使用。 不得用 Presence away 伪装后台状态。
c
uint64_t request_id = 0;
int32_t status = xhim_v1_client_set_app_runtime_state(
client,
XHIM_V1_APP_RUNTIME_BACKGROUND,
on_runtime_state,
context,
&request_id);
int32_t local_state = XHIM_V1_APP_RUNTIME_FOREGROUND;
status = xhim_v1_client_get_app_runtime_state(client, &local_state);set 的立即返回值为 XHIM_V1_STATUS_OK 时,Core 先线性化提交 local state/revision,再通过 /v1/session/app-runtime-state:set 上报当前经过 account、connection 和 credential epoch 校验的 device session。因此,即使旧服务端不支持、 网络失败、logout 竞态或请求取消,completion 也会收到非空的 xhim_v1_app_runtime_state_snapshot_t,且 local_state/local_revision 不回滚。若立即返回非 OK,请求未被接纳,也不会再产生 completion。
remote_reported != 0 只表示收到了服务端快照,不表示它仍是当前权威 结果。当 revision N 的迟到成功回调发生在本地已提交 N+1 之后, completion 稳定失败码为 app_runtime_state_superseded; remote_* 保留该迟到快照供诊断,local_* 则始终返回最新本地事实。 服务端未宣告 session.app_runtime_state 时,稳定失败码为 app_runtime_state_unsupported,C ABI status 为 XHIM_V1_STATUS_UNSUPPORTED。
- 重复设置同一状态是幂等的,不增加 local revision,
local_changed == 0。 - Background 只拒绝新的
publish_typing(is_typing=1);is_typing=0/stop 仍允许,已存在的 typing 也可依 TTL 收敛。 - Foreground 会立即唤醒正在等待的重连,并保证一次增量 Sync; 若 Sync 正在进行,任意多次前后台切换只合并为一次 pending Sync。
- 认证或重连再次 Ready 时,Core 会用新 revision 重报当前 state, 但该重报不会再触发 Sync,因此不会形成循环。logout/login 保留这个 OS 事实;新建 Client 默认为 Foreground。
xhim_v1_client_cancel_request只取消本次 completion/远端等待, 不回滚已提交的 local state,也不能撤销服务端已完成的 CAS。
xhim_v1_app_runtime_state_snapshot_t 是 64 位 ABI 下 72 bytes, local_state offset=8、local_revision offset=16、 remote_revision offset=32、reserved_u64 offset=56。读取未来追加字段前 必须按 struct_size 做容量判断。
新服务端对从未上报的旧 SDK device session 保持向后兼容: revision=0 不会被当成活跃前台会话来抑制 Push。即使已上报 Foreground,服务端也只在同一 device session 的短期实时 Presence 租约仍 有效时抑制该设备 Push;App 崩溃、被强制结束或 WebSocket 断开后,租约 清理/过期会恢复 Push,不会仅凭长寿命登录会话误判为仍在前台。 PostgreSQL 部署通过 0039 migration 保存非敏感 device state 并在事务内 执行 revision CAS;多实例都从 PostgreSQL 读取同一权威状态。Memory Store 只是单进程开发实现。
2.2 当前会话身份快照
xhim_v1_client_get_session_identity 是同步、只读、纯内存操作;它不访问 SQLite、不请求服务端,也不触发 callback。成功会返回一个由调用方拥有的 xhim_v1_session_identity_copy_t。用 xhim_v1_session_identity_copy_get 取得版本化 xhim_v1_session_identity_snapshot_t,最终必须调用 xhim_v1_session_identity_copy_destroy。copy_get 中的两个 byte view 归 copy handle 所有,在 destroy 前稳定;不需要依赖 Client 的生命周期。
c
xhim_v1_session_identity_copy_t *identity = NULL;
int32_t status = xhim_v1_client_get_session_identity(client, &identity);
xhim_v1_session_identity_snapshot_t snapshot = {0};
snapshot.struct_size = sizeof(snapshot);
snapshot.abi_version = XHIM_V1_ABI_VERSION;
if (status == XHIM_V1_STATUS_OK) {
status = xhim_v1_session_identity_copy_get(identity, &snapshot);
}
/* copy snapshot.account_id/self_user_id before destroy when retaining them */
xhim_v1_session_identity_copy_destroy(identity);available == 0 是正常且显式的“当前没有已认证身份”,不是用空字符串伪装 成功。此时 account_epoch == 0 且两个 ID 均为空。只有 Session 已原子发布 Ready 时才返回 available == 1、非零 epoch 和非空 account_id/self_user_id。create 后、认证过程中、logout、换号旧 epoch 撤销、credential/fatal 终止后都立即清空旧身份,绝不会把上一账号残留给 Wrapper。布局采用 struct_size + abi_version 前缀和保留尾部;调用方给 copy_get 的结构体必须初始化这两个字段。
2.3 E2EE 身份安全码
xhim_v1_client_get_e2ee_identity_safety 和 xhim_v1_client_set_e2ee_peer_verified 都是同步、纯本地操作, 不请求服务端。它们只在当前账号、会话 MLS 状态和对端设备 身份都已稳定投影时成功。
稳定符号为:
xhim_v1_client_get_e2ee_identity_safety;xhim_v1_client_set_e2ee_peer_verified;xhim_v1_e2ee_identity_safety_copy_get;xhim_v1_e2ee_identity_safety_copy_destroy。
c
xhim_v1_e2ee_identity_safety_copy_t *copy = NULL;
int32_t status = xhim_v1_client_get_e2ee_identity_safety(
client, conversation_id, peer_user_id, ©);
xhim_v1_e2ee_identity_safety_snapshot_t snapshot = {0};
snapshot.struct_size = sizeof(snapshot);
snapshot.abi_version = XHIM_V1_ABI_VERSION;
if (status == XHIM_V1_STATUS_OK) {
status = xhim_v1_e2ee_identity_safety_copy_get(copy, &snapshot);
}
/* copy every retained byte view before destroy */
xhim_v1_e2ee_identity_safety_copy_destroy(copy);safety_code 是展示给用户的规范安全码,verification_uri 可用于生成二维码。Wrapper 必须原样展示,不得在语言层重算或 改写。peer_devices 按 device_id 严格递增,同时绑定指纹、 device revision 和已核验状态。所有 view 都由 copy handle 持有。
标记已核验时,expected_safety_code 必须是用户刚比对的完整 安全码。Core 在同一事务内再次比对安全码与全部对端设备身份; 期间任一设备或指纹变化均返回 XHIM_V1_STATUS_SECURITY_FAILURE (107) / e2ee_identity_changed, 且不修改任何验证行。
旧 Core 缺少这四个符号时,Wrapper 必须稳定返回 e2ee_identity_unsupported,不得伪造空安全码。64 位布局中, peer-device snapshot 为 112 bytes,safety snapshot 为 144 bytes; 未来读取新字段必须先检查 struct_size。
2.4 账号生命周期错误与被踢事件
客户端只消费账号状态,不暴露封禁、解封或注销的后台治理写入 API。Server 与 Core 之间的稳定错误码只有 account_suspended 和 account_unregistered;管理员输入的原因、 备注和审计内容不得通过 error message、realtime reason、日志或 diagnostics 下发给 SDK。
认证、token 刷新或在线请求收到这两个错误时,公开 C 状态分别为:
XHIM_V1_STATUS_ACCOUNT_SUSPENDED(105);XHIM_V1_STATUS_ACCOUNT_UNREGISTERED(106)。
xhim_v1_error_t.stable_code 保留上述固定字符串,user_action 映射为 XHIM_V1_USER_ACTION_CONTACT_SUPPORT。Core 撤销当前 account/ connection/credential epoch,清理凭证并进入 XHIM_V1_CLIENT_STATE_CREDENTIAL_REQUIRED;旧 token 不能在该 Client 中继续 被采纳。
已建立的 realtime session 被账号状态撤销时,仅产生一个 XHIM_V1_EVENT_ACCOUNT_STATE_CHANGED (17) 事件,不再重复产生第二个 账号事件。payload_schema_version 为 XHIM_V1_ACCOUNT_STATE_EVENT_SCHEMA_VERSION (1),additive tail 为:
| 字段 | 值/语义 |
|---|---|
account_state | XHIM_V1_ACCOUNT_STATE_SUSPENDED 或 XHIM_V1_ACCOUNT_STATE_UNREGISTERED |
kicked_reason | XHIM_V1_KICKED_REASON_ACCOUNT_SUSPENDED 或 XHIM_V1_KICKED_REASON_ACCOUNT_UNREGISTERED |
account_state_revision | 正整数是 Server 权威 revision;0 仅表示旧 frame 没有 revision |
event_id | Server 事件标识,仅在 callback 期间借用 |
回调队列中的 event 由 Native OwnedEvent 保持;它会在入队时深拷贝 event_id 并按值保持三个 tail 标量,因此 Backend 原始 frame 的内存 可在返回后立即释放。应用需要在 callback 之后保留 event_id 时仍必须 在 callback 内复制 byte view。
Core 对正 revision 执行严格单调 fence:看到更高的正 revision 后, 忽略 0、更低或相同 revision;UNREGISTERED 是终态,不能被 SUSPENDED 或未来的 ACTIVE 事件复活。该事件与普通断线共用 connection generation/epoch/logout fence,迟到 callback 不能穿越换号、 logout 或 shutdown。
向后兼容边界:旧 Server 不下发新 reason 时不会伪造账号事件; 不识别的 session_revoked.reason 仍按旧版 session revoke/断线合同收敛, 不猜测 account state。只有精确 reason account_suspended / account_unregistered 才映射强类型事件。读取未来追加字段前必须检查 xhim_v1_event_t.struct_size;当前 64 位基线为 account_state offset=280、kicked_reason offset=284、 account_state_revision offset=288、sizeof(xhim_v1_event_t)=296。
3. 订阅与取消
xhim_v1_client_subscribexhim_v1_subscription_cancelxhim_v1_client_cancel_request
同一 subscription handle 只取消一次。每个被接纳的异步请求返回非零 request ID;普通请求取消使用这个 ID。事件与 completion 在 native 私有串行 callback 线程执行。
4. 消息
xhim_v1_client_send_textxhim_v1_client_send_messagexhim_v1_client_edit_text_messagexhim_v1_client_recall_messagexhim_v1_client_get_messagexhim_v1_client_retry_messagexhim_v1_client_cancel_messagexhim_v1_client_list_messagesxhim_v1_client_list_messages_afterxhim_v1_client_list_message_contextxhim_v1_client_insert_local_messagexhim_v1_client_set_message_local_extensionxhim_v1_client_get_message_local_metadataxhim_v1_client_delete_message_locallyxhim_v1_client_delete_messages_locallyxhim_v1_client_delete_all_messages_locallyxhim_v1_client_get_messages_by_server_idsxhim_v1_client_search_messagesxhim_v1_client_get_message_historyxhim_v1_client_delete_message_for_self
输入 byte views 在函数返回前由 Core 复制。Completion snapshot 中的 byte views 只在 callback 期间有效,Wrapper 必须先复制再恢复语言层 Future/Promise。
xhim_v1_message_input_t 尾部的 lifecycle_kind 和 burn_after_read_ms 用于持久/阅后即焚策略。尾部是 v1 加法扩展: 旧调用方结构体长度不包含它,或传 lifecycle_kind == 0,Core 都按 XHIM_V1_MESSAGE_LIFECYCLE_DURABLE 处理。 XHIM_V1_MESSAGE_LIFECYCLE_BURN_AFTER_READ 必须同时提供 1,000 到 2,592,000,000 毫秒的时长。不允许把该策略用于草稿。
xhim_v1_insert_local_message_input_t 只接受空 ID 或 xhim-local: 命名空间; 空 ID 由 Core 生成。这组操作使用类型化 xhim_v1_local_message_completion_callback,回调内的 xhim_v1_local_message_result_t 和嵌套 message/byte views 只在回调期间有效。 XHIM_V1_MESSAGE_FLAG_LOCAL_ONLY 同时出现在本地结果和消息 snapshot 的保留标志中,Wrapper 必须复制并暴露为强类型布尔值。
4.1 可选的可信业务通知投影
application/vnd.xhim.business-notification+json@1 依然是一条普通、 不可变的通用消息,不会改变 xhim_v1_message_snapshot_t 的 192 字节布局或数组 stride。新 Core 仅把它的 content_kind 标为 XHIM_V1_MESSAGE_CONTENT_BUSINESS_NOTIFICATION (6),然后提供 独立的可选强类型视图:
xhim_v1_message_business_notification_deep_copy;xhim_v1_message_business_notification_copy_get;xhim_v1_message_business_notification_copy_destroy。
deep_copy 仅当 content type、version 和 canonical payload 全部匹配时 返回 XHIM_V1_STATUS_OK。同一 content type 的未来版本、旧 Core 产生的短 struct_size、非 canonical/恶意 payload 都返回 XHIM_V1_STATUS_UNSUPPORTED;它们仍然保留在普通消息历史中, 不得被 Wrapper 当成整条消息的协议错误。只有空指针、非法 byte view 或 ABI version 才返回 XHIM_V1_STATUS_INVALID_ARGUMENT。
xhim_v1_business_notification_snapshot_t 是独立 104 字节结构: title offset=24、body offset=40、data_json offset=56。 schema_version=1、visibility=XHIM_V1_BUSINESS_NOTIFICATION_SERVER_VISIBLE 和 encrypted=0 都是显式安全属性;has_data 区分“未提供”和 “已提供空对象 {}”。copy_get 返回的 byte views 一直有效到 copy_destroy。
5. 会话与已读
xhim_v1_client_list_conversationsxhim_v1_client_search_conversationsxhim_v1_client_get_or_create_direct_conversationxhim_v1_direct_conversation_get_participant_user_idsxhim_v1_client_clear_conversationxhim_v1_client_hide_conversationxhim_v1_client_mark_conversation_readxhim_v1_client_mark_all_conversations_readxhim_v1_client_hide_all_conversationsxhim_v1_client_get_total_unread_countxhim_v1_client_list_conversation_peer_readsxhim_v1_client_set_conversation_preferencexhim_v1_client_set_conversation_preference_with_extensionxhim_v1_client_set_conversation_folderxhim_v1_client_list_conversation_foldersxhim_v1_client_set_conversation_pinned_messagexhim_v1_client_list_conversation_pinned_messagesxhim_v1_client_set_conversation_draftxhim_v1_client_get_conversation_draftxhim_v1_client_clear_conversation_draft
xhim_v1_conversation_preference_input_t.application_extension_json 是 v1 加法尾字段;当前结构为 88 字节,已发布的 72 字节前缀和所有旧字段偏移 保持不变。Core 在函数返回前深拷贝整个 JSON byte view,调用方可在返回后 释放原缓冲区。旧 struct_size 没有该尾字段时表示“不修改扩展”,即使 新 Server 已支持扩展,也必须保留服务端已有值,不能把缺失尾字段解释为 空值并意外清除。
非空值必须是最大 16 KiB、最大 64 层的 UTF-8 JSON object。 xhim_v1_conversation_preference_receipt_t 当前为 112 字节: application_extension_json 位于偏移 88,message_retention_seconds 位于偏移 104,原 104 字节前缀不变。receipt 和嵌套 byte view 只在 callback 返回前有效;Wrapper 需要保留时必须在 callback 内深拷贝。 响应只保证语义等价,不保证 JSON 原始字节与请求相同。receipt 没有独立 presence bit:只有 compatibility snapshot 包含 conversation.application_extension,或者本次专用 set_conversation_preference_with_extension 已成功时,该字段才可用;此时 零长度是权威 clear。legacy 调用连接未宣告能力的 Server 时,零长度表示 不可用,绝不能推断服务端扩展已被清空。
平台 Wrapper 只要要写入或显式清除 application_extension_json,就必须 weak-link 并调用 xhim_v1_client_set_conversation_preference_with_extension。该专用入口要求 完整 72 字节输入;符号不存在或返回 XHIM_V1_STATUS_UNSUPPORTED 表示当前 Core 不具备此能力。不得退回旧入口继续写扩展,因为旧 Core 可能只识别 56 字节前缀并对被忽略的尾字段返回成功。专用入口的零长度 bytes 是权威的 显式清除;capability 缺失则是“不可用”,两者不能混淆。旧入口仅保留给 不使用扩展字段的历史调用,并始终保持已有扩展。
xhim_v1_conversation_preference_input_t.option_mask 位于偏移 72, message_retention_seconds 位于偏移 80。只有 XHIM_V1_CONVERSATION_PREFERENCE_OPTION_MESSAGE_RETENTION 置位时, 保留秒数才具有 presence 语义;0 是权威关闭,非零值只允许 60..315360000。Wrapper 必须 weak-link xhim_v1_client_set_conversation_preference_with_options,并在提交前检查 conversation.message_retention。缺符号或缺 Server capability 统一返回 XHIM_V1_STATUS_UNSUPPORTED,不得回退基础入口。返回的 retention 只在 receipt 覆盖 112 字节且 capability 存在时可用;旧短结构是 unknown, 不能伪装为 0。
会话列表、搜索和离线读取不改动已发布的 112 字节 xhim_v1_conversation_snapshot_t 数组步长。每个 xhim_v1_conversation_page_t 通过原 16 字节保留尾部映射一个 application_extensions 并行数组;其 count 必须等于 conversation_count,且同下标的 conversation_id 必须与主数组一致; 兼容旧 Core 时 count 也可为 0,含义是扩展投影不可用,而不是全部会话的 扩展均被显式清空。 xhim_v1_conversation_extension_snapshot_t 为 56 字节,page 仍为 56 字节;所有 byte view 只在 callback 或离线 page 的既有借用 生命期内有效。
xhim_v1_client_search_conversations 与 xhim_v1_offline_reader_search_conversations 共用同一个加密本地投影、 置顶/活跃度排序和不透明游标。普通搜索覆盖会话 ID、最新安全摘要、 群资料及同步成员展示名;精确模式只匹配稳定会话 ID。该查询不发起 网络请求,适合启动页、桌面端搜索和通知扩展。
5.1 本地精确批量投影
xhim_v1_client_get_local_exact_batch 统一覆盖四类本地精确读取:
XHIM_V1_LOCAL_EXACT_CONVERSATIONS:只返回当前可见会话;XHIM_V1_LOCAL_EXACT_FRIENDSHIPS:只返回active=true好友完整记录;XHIM_V1_LOCAL_EXACT_GROUPS:只返回当前已加入群;XHIM_V1_LOCAL_EXACT_GROUP_MEMBERS:群投影必须已加入,成员也只返回 joined 记录,同时返回该群的group_revision。
XHIM_V1_LOCAL_EXACT_CONVERSATIONS 的 item 在原 16 字节保留尾部中 映射 conversation_application_extension_json;旧头仍按原大小和步长 遍历,新 Wrapper 在 callback 内深拷贝该 byte view。
输入是 1 到 XHIM_V1_MAX_LOCAL_EXACT_BATCH_SIZE(100)个非空、合法 UTF-8 稳定 ID。原始输入出现重复即同步返回 XHIM_V1_STATUS_INVALID_ARGUMENT,不会静默去重。只有群成员查询填写 conversation_id,其他三类必须留空。Core 在函数返回前深拷贝 query、 ID 数组和所有 byte view。
c
xhim_v1_bytes_view_t ids[] = { user_id_a, user_id_b };
xhim_v1_local_exact_batch_query_t query = {0};
query.struct_size = sizeof(query);
query.abi_version = XHIM_V1_ABI_VERSION;
query.kind = XHIM_V1_LOCAL_EXACT_GROUP_MEMBERS;
query.id_count = 2;
query.conversation_id = group_id;
query.ids = ids;
uint64_t request_id = 0;
int32_t status = xhim_v1_client_get_local_exact_batch(
client, &query, on_exact_batch, context, &request_id);结果 item_count 与输入数量完全相同且逐项同序。每项始终带原 requested_id;found == 0 明确表示缺失,found == 1 时只有由 page.kind 选择的 typed snapshot family 有效。这样可直接表达 “GetUsersInGroup matched/missing”,无需把缺失项从另一轮查询猜出来。 群成员页的 conversation_id/group_revision 用于在渲染期间检测 roster 版本变化。
四类查询都在当前账号绑定的 SQLite 连接锁内执行一条动态 VALUES CTE + LEFT JOIN SELECT,按输入 ordinal 排序;不会循环调用分页或 搜索来伪装批量,也不会跨账号读取。其 Ready、account Actor、immutable permit、account epoch、callback queue 与取消语义和 xhim_v1_client_list_conversations/social list 相同。这是本地只读 API, 不发网络请求;当前 C Client API 和现有 list 一样要求 Ready,离线扩展进程 继续使用独立 Offline Reader,而不是绕过 Session actor。
callback 的 xhim_v1_local_exact_batch_page_t、items 与嵌套 byte views 只在 callback 返回前有效。需要保留时必须在 callback 内调用 xhim_v1_local_exact_batch_deep_copy,之后以 xhim_v1_local_exact_batch_copy_get 取得由 copy handle 持有的 views,最终 调用 xhim_v1_local_exact_batch_copy_destroy。输入、item 和 page 都使用 struct_size + abi_version 前缀和保留尾部;旧 conversation/friendship/ group/member 数组元素的 stride 没有变化。
稳定错误映射:非法/重复/越界输入为 INVALID_ARGUMENT;群成员 scope 没有已加入群投影为 NOT_FOUND;logout/换号 epoch 竞态为 INVALID_STATE;SQLite 类错误为 STORAGE_ERROR;SDK 关闭为 SDK_SHUTDOWN。xhim_v1_client_cancel_request 仍保证原 callback 恰好一次收到 CANCELLED,但不会回滚已经完成的只读 statement。
6. 用户、Push 和设备会话
xhim_v1_client_get_current_user_profilexhim_v1_client_get_current_user_profile_with_extensionxhim_v1_client_get_user_profilesxhim_v1_client_get_user_profiles_in_conversationxhim_v1_client_resolve_user_by_phonexhim_v1_client_resolve_user_by_phone_with_extensionxhim_v1_user_profile_page_get_missing_user_idsxhim_v1_user_profile_page_get_application_extensionsxhim_v1_client_update_current_user_profilexhim_v1_client_update_current_user_profile_with_extensionxhim_v1_client_register_push_devicexhim_v1_client_disable_push_devicexhim_v1_client_list_device_sessionsxhim_v1_client_revoke_device_session
xhim_v1_client_resolve_user_by_phone 只接受规范 E.164(例如 +8613800138000),并返回最小公开用户资料;平台 Wrapper 可以在进入 C ABI 前完成本地手机号格式化。该接口不支持手机号片段、昵称或批量搜索。
xhim_v1_client_get_user_profiles_in_conversation 用于群成员资料页。 Core 会将群会话上下文交给服务端,由服务端同时校验请求人群成员身份和 member_profile_visible 群权限;不得仅在前端隐藏字段。
application_extension_json 是应用拥有的公开业务扩展,精确查询 陌生人资料时也会返回。严禁写入手机号、邮箱、实名、凭证、Token、 认证状态或其他个人敏感信息;private_profile_visible 只控制实名/邮箱/ 手机/部门等私密资料,不隐藏该公开扩展。
为保持旧二进制消费者的数组 stride, xhim_v1_user_profile_snapshot_t 的大小和所有 offset 永久不变。单资料 使用 *_with_extension API 和 xhim_v1_user_profile_envelope_completion_callback;批量资料使用 xhim_v1_user_profile_page_get_application_extensions 取得与 profiles 等长、同下标的并行 xhim_v1_user_profile_extension_snapshot_t 数组;旧 Core 未提供 sidecar 时 accessor 返回 OK、空指针和 count == 0,严格 Wrapper 已通过 user.profile_application_extension preflight 后才可将非零数组解释为 权威扩展。
xhim_v1_user_profile_snapshot_t.user_type 与旧头文件的 reserved_profile_u32 位于同一个 32-bit 匿名 union,因此结构仍为 224 字节,批量 profile 数组 stride 不变。值为 XHIM_V1_USER_TYPE_UNKNOWN/HUMAN/NOTIFICATION_SERVICE;其他非负 int32_t 值必须按 unknown 处理但保留 raw value。旧 Core 或缺少 protobuf field 17 时必须得到 UNKNOWN,不得按 ID 或显示名推断。 xhim_v1_direct_conversation_snapshot_t.peer_application_extension_json 使用原保留尾部的等尺寸视图,旧头文件仍可正确读取整个 direct snapshot。上述 envelope、并行数组与 JSON byte view 都只在回调期间有效, Wrapper 必须在回调返回前深拷贝。
xhim_v1_profile_update_input_t 的扩展为 additive tail:只有 XHIM_V1_PROFILE_UPDATE_APPLICATION_EXTENSION 置位且 struct_size 覆盖该尾部时才修改;未置位表示保持原值,零长度 bytes 表示清除。 非空值必须是 UTF-8 JSON object,规范化后不超过 16 KiB、嵌套不超过 64 层。成功回调返回服务端规范化后的权威字节,调用方不得将输入 原文当作最终持久值。旧 xhim_v1_client_update_current_user_profile 因回调无法携带权威扩展而拒绝 该 field bit;修改扩展必须调用 xhim_v1_client_update_current_user_profile_with_extension。
7. 好友、黑名单和群组
xhim_v1_client_send_friend_requestxhim_v1_client_send_friend_request_with_extensionxhim_v1_client_resolve_friend_requestxhim_v1_client_delete_friend_requestsxhim_v1_client_delete_friendshipxhim_v1_client_set_friend_remarkxhim_v1_client_set_friend_pinnedxhim_v1_client_set_friend_application_extensionxhim_v1_client_update_friendsxhim_v1_client_check_relationshipsxhim_v1_client_list_friend_requestsxhim_v1_client_list_friendshipsxhim_v1_social_page_get_friendship_remarksxhim_v1_client_set_blockxhim_v1_client_set_block_with_extensionxhim_v1_client_list_blocksxhim_v1_client_create_groupxhim_v1_client_create_group_with_extensionxhim_v1_client_change_group_membersxhim_v1_client_leave_groupxhim_v1_client_dismiss_groupxhim_v1_client_request_group_joinxhim_v1_client_request_group_join_with_extensionxhim_v1_client_resolve_group_joinxhim_v1_client_delete_group_join_requestsxhim_v1_client_change_group_governancexhim_v1_client_change_group_governance_with_extensionxhim_v1_client_moderate_group_member_messagesxhim_v1_client_list_groupsxhim_v1_social_page_get_group_profilesxhim_v1_client_list_group_membersxhim_v1_social_page_get_group_member_profilesxhim_v1_group_change_get_member_profilesxhim_v1_client_list_group_join_requestsxhim_v1_social_page_get_request_extensionsxhim_v1_client_search_group_membersxhim_v1_client_search_socialxhim_v1_client_get_social_summary
7.1 申请与方向关系扩展
好友申请、入群申请和当前账号的方向性黑名单均可携带一个由应用 完整拥有的 application_extension_json。非空值必须是有效 UTF-8 JSON object,服务端规范化后不超过 16 KiB、嵌套不超过 64 层;不得写入 Token、密码、密钥或消息正文。成功回调和同步投影以服务端返回的 canonical bytes 为准,Wrapper 不能用请求原文覆盖。
- 好友申请扩展只对发起方和接收方可见。携带扩展发送必须调用
xhim_v1_client_send_friend_request_with_extension,并在xhim_v1_friend_request_envelope_t.extension中读取权威回显。xhim_v1_friend_request_resolution_snapshot_t使用原保留尾部返回被处理 申请的扩展。 - 好友申请列表的旧
xhim_v1_friend_request_snapshot_t数组 stride 保持 不变。在 callback 内调用xhim_v1_social_page_get_request_extensions取得与item_count等长、同下标的平行扩展数组;request_id是额外的对齐校验。旧 Core sidecar 缺失时 accessor 以OK + count == 0表示不可用,不能解释为每条 申请都携带权威空扩展。 - 入群申请扩展只对申请人与群主/管理员可见。需要审批时会保留至
xhim_v1_group_join_request_snapshot_t.application_extension_json;无需 审批的直接入群必须清除这份审批 metadata。携带扩展必须调用xhim_v1_client_request_group_join_with_extension。 - 黑名单扩展只属于当前认证账号的单向关系,不返回给被拉黑方。
has_application_extension == 0表示保持原值;等于 1 且 bytes 为空 表示显式清除。存在性写入必须走xhim_v1_client_set_block_with_extension。
三个 *_with_extension 都是 capability-fenced 新符号。平台 Wrapper 必须 weak-link/动态探测;旧 Core 缺少符号时稳定返回 UNSUPPORTED,不得 回退到历史 API 并把被忽略的尾字段伪装成成功。为保持已发布 ABI, 历史 send_friend_request / set_block / request_group_join 只读取 旧结构前缀,不应使用它们修改扩展。所有 envelope、page 平行数组和 嵌套 byte view 均仅在 callback 期间有效,Wrapper 必须在 callback 返回前 深拷贝。
64 位 ABI 基线:
| 结构 | sizeof | 关键 offset |
|---|---|---|
xhim_v1_social_request_extension_snapshot_t | 56 | request_id=8, application_extension_json=24 |
xhim_v1_social_request_extension_array_t | 16 | items=0, count=8 |
xhim_v1_friend_request_envelope_t | 216 | request=8, extension=144 |
xhim_v1_send_friend_request_input_t | 96 | application_extension_json=80 |
xhim_v1_set_block_input_t | 64 | has_application_extension=12, application_extension_json=48 |
xhim_v1_request_group_join_input_t | 72 | application_extension_json=56 |
xhim_v1_block_snapshot_t | 80 | application_extension_json=64 |
xhim_v1_group_join_request_snapshot_t | 128 | application_extension_json=112 |
xhim_v1_social_page_t | 112 | request_extensions=96 |
xhim_v1_friend_request_resolution_snapshot_t | 232 | request_application_extension_json=216 |
xhim_v1_client_update_friends() 是不破坏旧单目标 API 的原子批量属性 写入入口。xhim_v1_update_friends_input_t 以等长的 peer_user_ids / expected_revisions 平行数组传入 1...100 个不重复 好友;Core 在同步返回前深拷贝数组、字符串和 JSON,调用方 不需保持输入内存。present_fields 必须至少包含 XHIM_V1_FRIENDS_UPDATE_REMARK、XHIM_V1_FRIENDS_UPDATE_PINNED 或 XHIM_V1_FRIENDS_UPDATE_APPLICATION_EXTENSION 之一,可以一次修改多个 统一属性。字段未出现表示不修改;已出现的空 remark/空 application extension 表示显式清除,is_pinned=0 表示显式取消 置顶。扩展必须是最多 16 KiB、嵌套深度小于 64 的严格 JSON object;服务端返回 canonical JSON。
服务端对全部目标先做 active-friend 和 exact-revision CAS 校验,再在 单一事务中全部写入;任一目标失败都不会留下部分结果。每个目标 无论修改几个字段,共享 property revision 只加一次。同一 mutation_id 重试必须复用完全相同的目标顺序、revision 和字段, 重放返回与首次完全相同、且按输入顺序排列的权威 friendship 快照。friendships 与 properties 是等长平行数组,其视图只在 callback 返回前有效,需长期保留时必须在 callback 内复制。
xhim_v1_create_group_input_t 的可选加法尾部 administrator_user_id_count / administrator_user_ids 用于在创建成员的同一 服务端事务中写入初始管理员。每个管理员必须同时存在于 member_user_ids;旧调用方的较小 struct_size 继续按“无初始管理员”解释, 不会读取尾部内存。
xhim_v1_client_moderate_group_member_messages 仅提供给群主或管理员治理 某一群成员的历史消息。该接口写入服务端权威的 sender-watermark 墓碑:仅隐藏该发送者在 through_server_sequence 及之前的消息, 不物理删除规范记录,也不影响 cutoff 之后的新消息。传入 0 时由 服务端在事务内固化为当时已存在消息的最大序号;mutation_id 和 expected_revision 分别提供幂等与并发栅栏。回调中的 xhim_v1_group_member_message_moderation_snapshot_t 及其嵌套字符串仅在 回调期间有效,平台 Wrapper 必须在返回前深拷贝。
xhim_v1_client_search_social 在已经由服务端同步并落入加密数据库的社交投影上 执行有界分页查询,支持好友申请、好友、群聊、群成员和入群申请。传入群成员类型时 必须同时提供 conversation_id;state_filter 只适用于好友申请和入群申请,游标 只能继续同一个类型和同一组查询条件。该接口不额外发起 HTTP 请求,因此登录后的 弱网和短暂离线阶段仍可读取最近一次完整同步结果。
xhim_v1_client_delete_friend_requests 和 xhim_v1_client_delete_group_join_requests 每次接受 1...100 个不重复申请 ID。 删除只作用于当前账号的多端同步收件箱:Core 收到服务端 deleted 墓碑后会删除 本地 SQLite 投影;对方、群主/管理员仍可保留其有权查看的申请,服务端也保留规范 记录和审计轨迹。相同 mutation_id 只能重放完全相同的 ID 集合。
群成员查询可在原 v1 预留尾部传入 joined_from_ms 和 joined_before_ms,语义是左闭右开区间,0 表示不限制。指定时间区间后 query 可以为空;旧二进制对应的两个预留值为 0,行为不变。 新版结构尾部的 group_member_role_mask 进一步提供角色筛选: bit 0 为群主、bit 1 为管理员、bit 2 为普通成员,0 表示全部角色。 位可组合,例如 0x3 一次返回群主和管理员。只指定角色时 query 也可为空;查询仍在当前账号的加密本地投影上原子分页, 并返回同页的群版本号。excluded_user_id_count / excluded_user_ids 可再排除 0...100 个不重复的 UTF-8 用户 ID; 排除在 LIMIT 之前由同一条 SQL 执行,因此不会出现端侧过滤后页面变短。 仅排除用户时 query 也可为空。小于新尾部大小的旧调用方按角色掩码 0、 无排除项处理。
非零角色掩码或排除列表必须通过 xhim_v1_client_search_group_members 调用。该独立导出符号是能力栅栏: 旧 Core 缺少符号时 Wrapper 必须明确返回 unsupported,不得改调 xhim_v1_client_search_social 并静默忽略新筛选条件。只有基础关键词/入群时间 查询可以继续使用旧通用符号。
xhim_v1_client_get_social_summary 返回有效好友数、收到的待处理好友申请数、已加入 群聊数,以及当前用户作为群主或管理员需要处理的入群申请数。四个值来自同一账号 隔离的本地投影,可直接驱动通讯录红点和待办入口。
本地社交投影完整性证明
xhim_v1_client_check_joined_group_projection 与 xhim_v1_client_check_group_member_projection 是只读的加密 SQLite 完整性检查。两者只在已认证的 Ready 账号 epoch 内接纳,并受 xhim_v1_client_cancel_request 约束;登出、换号或 epoch 变化不会把旧账号结果 交给新账号。调用成功但 complete == 0 是正常的结构化结果,不是 API 错误, 调用方必须读取 reason,不能把本地行数相等自行解释成完整。
证明来源严格受限:
- 群集合必须从空 cursor 开始连续重放到 Session 协调器确认的 high-watermark,或来自一次服务端 Projection Snapshot 的群集合替换;marker cursor 还必须与当前 sync checkpoint 完全一致。
- 群成员只有在群创建产生的完整
GroupChanged(成员数完全相等、成员 ID 唯一且恰好一个正确群主)落库后才能建立 COMPLETE marker。之后只有连续的revision + 1增量且落库后的成员数/群主约束仍成立,才会延续该证明;revision 跳号会把 marker 降为 incomplete。 - 旧数据库升级不会根据现有行数补造 marker;服务端当前登录 Snapshot 只携带 当前用户自己的 Membership,也不会被当作完整群成员全集。
若既有群缺少证明,调用 xhim_v1_client_refresh_group_member_projection可显式重建该群的本地成员全集。 该请求必须在认证后的 Ready 账号 epoch 内调用,使用当前 SessionBackend 分页访问 /v1/social/group-members:list,不接受任何 query/时间/角色/排除过滤。 Core 要求每页非零且一致的 group_revision、跨页严格递增且唯一的 user_id、 严格游标和自洽的 has_more。所有页先完整缓冲,之后在单个加密 SQLite 事务中 复核 joined/revision/member_count/当前成员/唯一且匹配的群主,仅在全部成立时原子替换 active members 并建立 COMPLETE marker。失败、版本漂移、登出或取消都不会落入部分页。
xhim_v1_client_cancel_request 与最终事务提交线性化:取消返回 XHIM_V1_STATUS_OK 即保证刷新不会提交,返回 XHIM_V1_STATUS_INVALID_STATE 表示操作已经解决(可能是事务已经先胜出)。revision 漂移返回 XHIM_V1_STATUS_INVALID_STATE、retryable=1、stable code group_member_projection_revision_stale;其它分页结构错误 fail-closed,不会被表达成成功。
回调中的 xhim_v1_social_projection_integrity_snapshot_t 和 conversation_id 只在回调期间借用。需要异步持有时,在回调返回前调用 xhim_v1_social_projection_integrity_deep_copy,之后通过 xhim_v1_social_projection_integrity_copy_get 读取,并最终调用 xhim_v1_social_projection_integrity_copy_destroy。64 位 ABI 中该 snapshot 为 88 字节,kind=8、complete=16、group_revision=24、 conversation_id=56、reserved_u64=72。
xhim_v1_client_check_relationships 接受 1...500 个不重复 user ID,异步返回 顺序一致的 xhim_v1_relationship_status_page_t。回调中的 page、数组和 嵌套字符串都只在回调期间有效,Facade 必须在返回前深拷贝。
xhim_v1_group_governance_action_t 的 XHIM_V1_GROUP_GOVERNANCE_SET_MEMBER_NICKNAME 用于修改当前登录成员自己的群昵称。 输入通过 xhim_v1_group_governance_input_t.member_nickname 提供;调用方不能指定 另一个目标成员。xhim_v1_group_member_snapshot_t.nickname 返回该成员的群内昵称。 这些字段复用了 v1 预留区,结构总大小保持不变,旧 Wrapper 会安全忽略。
XHIM_V1_GROUP_GOVERNANCE_SET_NEW_MEMBER_HISTORY_VISIBILITY 通过 xhim_v1_change_group_governance_input_t.new_member_history_visible 提交。 xhim_v1_group_profile_snapshot_t.new_member_history_visible 是服务端权威群策略。 两个字段都复用原有 64 位预留尾部拆分后的 32 位空间, xhim_v1_group_profile_snapshot_t 仍为 88 字节;governance input 的 184 字节前缀与全部旧字段偏移不变,后续扩展只追加在尾部。
xhim_v1_group_member_snapshot_t 继续保持 88 字节的旧 ABI 步长。 群成员的实时用户资料与成员业务扩展通过并行的 xhim_v1_group_member_profile_snapshot_t 返回: xhim_v1_social_page_get_group_member_profiles 用于群成员分页/离线读取, xhim_v1_group_change_get_member_profiles 用于群增量变更。两个并行数组必须 与原成员数组等长且同下标,Wrapper 必须在回调返回前深拷贝 display_name/avatar_url 与 application_extension_json。
XHIM_V1_GROUP_GOVERNANCE_SET_MEMBER_APPLICATION_EXTENSION 使用 xhim_v1_change_group_governance_input_t 的加法尾字段 member_application_extension_json。较旧调用方仍可传 184 字节前缀; 该字段位于 200 字节前缀内(当前结构含后续群本体扩展尾, 总大小为 216 字节)。空 bytes 表示清除,非空值必须是 最大 16 KiB、最大嵌套深度 64 的 UTF-8 JSON object;服务端会再次 校验并规范化键顺序。
xhim_v1_group_snapshot_t.application_extension_json 精确复用原 reserved_u64[2],因此 snapshot 仍为 112 字节,旧头的连续两元素 数组步长不变。xhim_v1_create_group_input_t.application_extension_json 是 104 字节新结构的加法尾字段;旧前缀省略该字段即以空值建群。 要在建群时传入或显式传递空扩展,Wrapper 必须 weak-link xhim_v1_client_create_group_with_extension;旧 Core 缺少该符号时必须对调用方 报 XHIM_V1_STATUS_UNSUPPORTED,不得退回旧建群入口并假定尾字段已生效。
XHIM_V1_GROUP_GOVERNANCE_SET_APPLICATION_EXTENSION 仅读取 xhim_v1_change_group_governance_input_t.application_extension_json,新结构为 216 字节。空 bytes 显式清除,非空值使用相同的 UTF-8 / JSON object / 16 KiB / 64 层限制。以旧 200 字节前缀调用这个新 action 会返回 XHIM_V1_STATUS_UNSUPPORTED,不会静默降级为其他治理操作。 其他 action 携带非空群扩展会被拒绝,避免形成混淆的部分替换语义。 这个 action 必须通过 xhim_v1_client_change_group_governance_with_extension 发起;它只接受 SET_APPLICATION_EXTENSION 且强制完整 216 字节输入。符号不存在或 返回 XHIM_V1_STATUS_UNSUPPORTED 时,Wrapper 应稳定暴露“当前 Core 不支持群本体扩展”,不得改调旧治理入口。
8. Presence 与 Typing
xhim_v1_client_publish_presencexhim_v1_client_query_presencexhim_v1_client_subscribe_presencexhim_v1_client_unsubscribe_presencexhim_v1_client_list_presence_subscriptionsxhim_v1_client_publish_typingxhim_v1_client_query_typingxhim_v1_client_publish_custom_signal
短暂状态通过 XHIM_V1_EVENT_PRESENCE_CHANGED 和 XHIM_V1_EVENT_TYPING_CHANGED 发送,必须读取 sequence、event ID 和过期时间。 xhim_v1_client_query_presence 用于获取一组已授权用户的服务端快照, 结果顺序与输入 user ID 顺序一致;其中任一用户不在当前账号的好友、 共同群聊或自身可见范围内时,整个请求会拒绝,不泄露部分状态。
xhim_v1_client_subscribe_presence 会先执行同一授权快照查询, 只有查询整体成功后才原子提交账号级订阅集。 xhim_v1_client_unsubscribe_presence 是幂等本地退订, xhim_v1_client_list_presence_subscriptions 返回排序且深拷贝的当前订阅集。 客户端首次调用订阅或退订后进入显式模式: Presence 实时事件只投递集合中的用户;登出或销毁会话会清空集合并恢复授权受众兼容模式,普通断线重连不丢失订阅。
xhim_v1_client_query_typing 读取指定会话成员当前未过期的输入租约, 不发布实时事件、不推进 sequence。从未输入或租约已过期时, 返回 is_typing = false 且 sequence = 0;发布与实时事件仍要求非零 sequence。
xhim_v1_presence_snapshot_t.active_platforms 和 xhim_v1_typing_snapshot_t.active_platforms 是 ABI v1 可加尾部;旧 Wrapper 必须先按 struct_size 判断是否可读。指针、数组和订阅页中的 user ID 都只在 completion callback 期间有效,平台 Facade 必须在回调返回前深拷贝。
xhim_v1_client_publish_custom_signal 接收 xhim_v1_publish_custom_signal_input_t,载荷为 1...65536 字节,TTL 为 0(服务端默认)或 1000...10000 毫秒。完成回调使用 xhim_v1_custom_signal_completion_t 和仅在回调期间有效的 xhim_v1_custom_signal_snapshot_t。接收端通过 XHIM_V1_EVENT_CUSTOM_SIGNAL_RECEIVED 读取会话、发送者、content type/version、 payload、sequence 和过期时间。该信令只转发给当前在线会话成员, 不写入消息、Outbox 或 Sync;需要离线送达与历史留存时必须发送自定义消息。
9. 通话 Session 与恢复
xhim_v1_client_invite_callxhim_v1_client_accept_callxhim_v1_client_reject_callxhim_v1_client_end_callxhim_v1_client_list_call_signalsxhim_v1_client_recover_call_sessionsxhim_v1_call_session_deep_copyxhim_v1_call_session_copy_getxhim_v1_call_session_copy_destroyxhim_v1_call_accept_deep_copyxhim_v1_call_accept_copy_getxhim_v1_call_accept_copy_destroyxhim_v1_call_signal_page_deep_copyxhim_v1_call_signal_page_copy_getxhim_v1_call_signal_page_copy_destroy
invite/accept/reject/end 是有权限校验的高层 Call Session 操作, 不绑定任何 RTC 厂商。mutation_id 是一次逻辑操作的稳定幂等键, 重试只能重用同一语义的 ID。Call Session 和信令中的参与者、邀请者、 conversation 及单调递增 sequence 由服务端与 Core 双重校验; 乱序、重复、过期凭证或 ended 会话复活都会被拒绝。
xhim_v1_client_list_call_signals 是不改变本地恢复游标的显式分页读取。 xhim_v1_client_recover_call_sessions 从当前账号的加密 SQLite 游标继续, 把整页信令、去重投影与 next offset 在同一事务内提交,并返回 可恢复的未结束会话。登出、账号 epoch 变更或新连接 generation 会屏蔽旧回调。
Completion 中的 session/page 及所有嵌套指针只在回调期间有效。 Wrapper 需在回调返回前调用对应 *_deep_copy,后续用 *_copy_get 获得一组在 opaque copy handle 销毁前稳定的 views,最终必须调用 对应 *_copy_destroy。copy_get 的输出结构仍必须由调用方先填写 struct_size 和 XHIM_V1_ABI_VERSION。
accept 返回的 RTC token/endpoint 是短期凭证:它可以在 callback 或上述 accept deep-copy handle 的内存生命期内传给可插拔 RTC Provider,但 Core 不会把 token、 endpoint 或 provider credential 写入 SQLite。恢复信令中如果携带凭证, Wrapper 也必须校验 expires_at_ms 后立即交给 RTC Provider,不得持久化。
xhim_v1_client_cancel_request 保证已接纳的 Call 请求在本地只完成一次并丢弃 迟到回调;它不是服务端事务回滚,已被服务端接纳的幂等 mutation 仍可能完成,应通过后续 recovery 收敛最终状态。
10. 媒体任务与缓存
xhim_v1_client_create_media_uploadxhim_v1_client_create_media_downloadxhim_v1_client_get_media_taskxhim_v1_client_cancel_media_taskxhim_v1_client_open_media_cache_readerxhim_v1_media_cache_reader_get_sizexhim_v1_media_cache_reader_readxhim_v1_media_cache_reader_destroy
媒体任务 snapshot 不包含本地路径、credential 或 signed URL。Cache Reader 与 Client 是不同 opaque handle,必须显式 destroy。
11. 离线只读
xhim_v1_offline_reader_createxhim_v1_offline_reader_list_messagesxhim_v1_offline_reader_search_messagesxhim_v1_offline_reader_list_conversationsxhim_v1_offline_reader_search_conversationsxhim_v1_offline_reader_list_friend_requestsxhim_v1_offline_reader_list_friendshipsxhim_v1_offline_reader_list_groupsxhim_v1_offline_reader_list_group_membersxhim_v1_offline_reader_list_blocksxhim_v1_offline_reader_list_group_join_requestsxhim_v1_offline_reader_destroy
Offline Reader 不得启动 Transport、Sync 或 Outbox。数据库 Key Provider 的输出 只用于打开加密数据库,不能写入错误、日志或 diagnostics。
12. Message Enrichment Provider SPI
Message Enrichment 是与 Client Session 解耦的客户端扩展,为语音转文字和 文本翻译提供稳定的、供应商中立的 C ABI。XHIM 不内置、不伪装 任何语音或翻译模型;客户可以在 Provider vtable 中适配阿里云、腾讯云、 经审计的其他服务或本地模型。
稳定导出为:
xhim_v1_enricher_createxhim_v1_enricher_transcribexhim_v1_enricher_translatexhim_v1_enricher_cancelxhim_v1_enricher_shutdownxhim_v1_enricher_destroyxhim_v1_enrichment_provider_sink_retainxhim_v1_enrichment_provider_sink_releasexhim_v1_enrichment_provider_sink_progressxhim_v1_enrichment_provider_sink_complete
12.1 Provider 所有权与借用边界
xhim_v1_enricher_create 会在返回前复制 config、 xhim_v1_enrichment_provider_descriptor_t、 xhim_v1_enrichment_provider_vtable_t 和 xhim_v1_enrichment_observer_vtable_t。provider_context 只在 create 返回 XHIM_V1_STATUS_OK 时转移给 Enricher;失败时仍归调用方。成功后 Core 先调用 Provider shutdown,再且仅调用一次 destroy。Provider 必须在 shutdown 返回前停止业务线程并释放它保留的所有 sink,不得在 destroy 后继续访问 context。
Provider start_transcription / start_translation 收到的 request 、字符串、 二进制内容、metadata 数组只在该 start 回调返回前有效。异步 Provider 必须在返回前深拷贝它需要保留的请求字段。start 中的 sink 也是借用的: 同步 progress/completion 可以直接调用;要跨越 start 边界,必须先调用 xhim_v1_enrichment_provider_sink_retain,并在最后一个回调、cancel 或 shutdown 清理时精确调用一次 xhim_v1_enrichment_provider_sink_release。
Provider 可以同步回调、重复回调或在 cancel/timeout 后迟到回调;Core 通过 request epoch 保证应用层 completion exactly-once,并屏蔽迟到的 progress/result。 C++ Provider 回调中意外抛出的异常会被 ABI 桥接捕获并分类为 internal/provider 失败;纯 C 或其他 FFI 回调仍不得跨 ABI 边界 unwind。
12.2 请求、回调和取消
transcribe / translate 在返回前深拷贝调用方输入。调用方需为 xhim_v1_enrichment_submit_result_t 填写 struct_size 与 XHIM_V1_ABI_VERSION。该结构中的 failure_code 是调用方内联持有的拷贝, 不是悬空 view。Provider 允许在 start 内同步回调,因此 progress/completion 可以在 submit 返回前发生;应用应使用自己传入的 request_id 关联,不要在 回调中假设 out_result 已写完。
Observer 中的 progress/completion、segment 和 metadata 都是回调期借用,需要 长期使用时必须在回调返回前复制。cancel 是幂等的:首次取消返回 XHIM_V1_ENRICHMENT_CANCELLED,已终态请求返回 XHIM_V1_ENRICHMENT_ALREADY_FINISHED,不会发生第二个 completion。 shutdown 幂等且可以从 progress/completion 重入;destroy 必须与其他 API 外部串行,并且不得在回调栈上执行。
12.3 E2EE 与数据最小化
Enrichment 不得绕过 E2EE。input_boundary 必须显式为“客户端明文且已授权” 或“客户端解密 E2EE 且已授权”;UNSPECIFIED 必须失败。远程 Provider 还需要每次请求显式设置 allow_remote_processing=1。语音输入只能是客户端 已解密 bytes 或短期 secure-local handle;该 handle 不是服务端路径,也不能让 服务端取得解密权限。
Core 默认不持久化 plaintext、audio bytes、secure handle、Provider credential 或 Provider 输出,也不把这些内容写入日志/diagnostics。 allow_provider_persistence=0 时 Provider 也必须不留存输入或输出;如果客户显式允许 Provider 留存,仍由客户与 Provider 自己的合规、保留期和删除策略负责, XHIM 不会代为持久化。
12.4 64 位 ABI 基线
| 结构 | sizeof | 关键 offset |
|---|---|---|
xhim_v1_enrichment_buffer_view_t | 40 | data=8, len=16 |
xhim_v1_enrichment_metadata_entry_t | 104 | key=8, value=48 |
xhim_v1_enrichment_transcription_request_t | 312 | timeout_ms=208, privacy=216, provider_metadata=256 |
xhim_v1_enrichment_translation_request_t | 272 | timeout_ms=168, privacy=176, provider_metadata=216 |
xhim_v1_enrichment_output_t | 280 | segments=144, provider_metadata=224 |
xhim_v1_enrichment_progress_t | 168 | request_epoch=48, provider_metadata=112 |
xhim_v1_enrichment_completion_t | 520 | request_epoch=48, failure=104, output=224 |
xhim_v1_enrichment_submit_result_t | 216 | request_epoch=16, failure_code=40 |
xhim_v1_enrichment_provider_vtable_t | 80 | start_transcription=8, destroy=40 |
所有字符串和二进制 view 都使用带 struct_size/abi_version 的 xhim_v1_enrichment_buffer_view_t;metadata/segment 数组使用同样带版本前缀的 list 和 element 结构。新字段只能追加,不能改变上述 64 位基线。
13. 状态码
立即返回值表示请求是否被接纳:
XHIM_V1_STATUS_OK:请求已接纳,最终结果仍等待 completion;- 参数、状态或已关闭错误:不会再产生 completion;
- 请求接纳后只完成一次,成功或失败二选一。
完整状态码和稳定错误字段见 模型、枚举与错误。Bridge 必须保留 domain + stable_code、retry 信息、user action、operation ID 和 trace ID, 不能只抛一个 message 字符串。
14. struct_size 兼容
所有公开结构:
- 调用方写入自己的
struct_size和XHIM_V1_ABI_VERSION; - 读取 native snapshot 尾部前检查
struct_size; - 新字段只追加在 v1 兼容尾部;
- 不读取未知尾部,不要求旧版本认识新枚举值;
- 未知枚举保留原始数值并映射到平台
unknown。
导出符号基线位于 compatibility/abi-baseline/xhim_v1.exports。 文档门禁会检查基线中的每个导出都在本页出现。