Skip to content
Jinnang233 edited this page Oct 8, 2026 · 3 revisions

Mod API

← 返回主页

本页按 Krypt04Mcg 0.29.0 的公开 API 文档核对。0.28/0.29 的聊天 HUD 与大分片修改未更改此流式 API 的 wire format。公开入口位于 dev.krypt04mcg.api。本页描述的是 0.29.0 raw encrypted channel API,而不是 0.18/0.19 的旧 Reliable Data API。

Important

Breaking change: DataTransfer、TransferResult、DATA/ACK/NACK envelope、completion receipt、应用层 retransmission,以及旧 krypt04mcg:data / krypt04mcg:file_share payload 已经移除。旧 Wiki 中关于 DELIVERED / TIMEOUT / BACKPRESSURE 的说明不再适用于当前代码。

前置条件

公开数据 / stream API 要求:

  1. 双方客户端安装兼容版本 Krypt04Mcg;
  2. 双方都已经导入对方当前公钥,且没有把对方标记为 DISTRUSTED;
  3. 普通 Mod API 设置 enableDataApi=true;
  4. 服务器 Relay 正确声明并转发 krypt04mcg_stream:control 和双方共同支持的 raw data slots;
  5. connect(...)、打开 stream、listener/control 生命周期调用发生在 Minecraft client thread。

文件分享是特殊内部 channel:它使用同一套 raw stream transport,但是否允许由 enableFileSending / enableFileReceiving 控制,不要求 enableDataApi=true。文件分享命令本身仍要求 chatSendMode=CUSTOM_PAYLOAD。

Minecraft 通道

apiChannelCount 默认 16,范围 1..256。客户端启动时预注册:

krypt04mcg_stream:control
krypt04mcg_stream:data/0
krypt04mcg_stream:data/1
...
krypt04mcg_stream:data/(n-1)

修改 apiChannelCount 后必须重启,因为 Minecraft payload type 在初始化时注册。

krypt04mcg_stream:control

ControlPayload.Kind 当前有:

EXCHANGE
OPEN
ASSIGNED
READY
END
RESET
ABORT

control payload 包含:

kind
peer
stream UUID
slot
application channel
sessionId
sequence
body <= 30000 bytes

EXCHANGE 的 body 是现有 SESSION_EXCHANGE 加密包;其余客户端控制消息使用从 API Session 派生的 HMAC-SHA256 tag。ASSIGNED 和 ABORT 是 Relay 生命周期通知。

krypt04mcg_stream:data/N

每个 raw data payload 只有:

XChaCha20-Poly1305 ciphertext || 16-byte tag

明确 没有:

  • sender / receiver
  • application channel
  • stream UUID
  • 显式 sequence
  • 显式 nonce
  • inner length
  • Base64 / JSON
  • fragment ID
  • per-record ACK / NACK

Minecraft Custom Payload 本身提供有序、可靠传输和 record boundary;Krypt04Mcg 只把该 boundary 当 AEAD record boundary。

每条 record 最多 16 KiB 明文。nonce 为 24 字节,由 stream UUID 与方向本地隐式 record counter 构造;counter 不在 data channel 上传输。

最小 Socket 示例

import dev.krypt04mcg.api.Krypt04McgApi;
import dev.krypt04mcg.api.KryptSocket;

Krypt04McgApi.registerSocketReceiver("example:stream", socket -> {
    // listener 在 Minecraft client thread 执行。
    // read() 可能阻塞,因此把读取交给你自己的后台 executor。
    executor.execute(() -> {
        try {
            byte[] bytes = socket.getInputStream().readAllBytes();
            // 这里得到认证后的完整字节流;再按你的应用协议解析。
        } catch (java.io.IOException failure) {
            // reset / auth failure / truncation / timeout / disconnect 等。
        }
    });

    // 本端没有数据要回写:半关闭输出;输入仍可继续读到认证 EOF。
    socket.close();
});

KryptSocket socket = Krypt04McgApi.connect("Bob", "example:stream");

// 发送方应跨 tick 保存 socket + bytes + offset,并根据容量推进。
int count = Math.min(socket.writableBytes(), bytes.length - offset);
if (count > 0) {
    socket.getOutputStream().write(bytes, offset, count);
    offset += count;
}
if (offset == bytes.length) {
    socket.close(); // queued bytes 发完后再发送认证 END
}

Krypt04McgApi

建立 Session handle

KryptSession session = Krypt04McgApi.connect("Bob");

connect(player) 只返回本地 handle;它不会暴露 session secret。

session.peer();
session.isReady();
session.sessionId();
session.ready();
session.send("example:channel", bytes);
session.close();

语义:

  • ready():等待本地 API Session 交换完成。
  • isReady():当前 handle 是否仍有效且 exchange 已成功。
  • sessionId():仅 ready 时返回当前 ID,否则 null。
  • send(...):convenience one-shot stream;即使 Session 尚未 ready,也可先排入 bounded channel pool。
  • close():只关闭这个本地 API connection handle 并取消它的 queued sends;不会清除聊天 Session。

打开全双工 stream

KryptSocket socket = Krypt04McgApi.connect("Bob", "example:stream");

application channel 必须匹配:

[A-Za-z0-9_.:-]{1,256}

注册 Socket listener

Krypt04McgApi.registerSocketReceiver("example:stream", socket -> {
    // client-thread callback
});

Krypt04McgApi.unregisterSocketReceiver("example:stream");

同一个 channel 如果同时存在 socket receiver 和 byte receiver,socket receiver 优先。

Convenience send

Krypt04McgApi.send("Bob", "example:event", bytes);
Krypt04McgApi.send("example:event", bytes); // peer 来自 apiReceiver

当前 send(...):

  • 返回 void;
  • 内部打开一个 stream、写入 bytes、然后 half-close output;
  • 单次 bytes 不能超过 KryptSocket.MAX_BUFFERED_BYTES = 1 MiB;更大的数据应自己使用 KryptSocket 分批写;
  • 没有 delivery receipt,也没有“对方持久化成功”的语义。

Byte receiver

Krypt04McgApi.registerReceiver("example:event", (sender, bytes) -> {
    // client-thread callback
});

也有单参数 overload:

registerReceiver(String channel, Consumer<byte[]> receiver)
registerReceiver(String channel, BiConsumer<String, byte[]> receiver)
unregisterReceiver(String channel)

Warning

当前 byte receiver 收到的是 stream portion,不是“完整的一条应用消息”。底层每次解密一个 raw record 就可能调用一次 callback。不要假设一次 send(bytes) 必然对应一次 receiver callback。需要消息边界、长度、类型、请求/响应或事务语义时,请使用 KryptSocket 并定义自己的 framing。

KryptSocket 线程与生命周期

公开属性:

peer()
channel()
streamId()
getInputStream()
getOutputStream()
isClosed()
isFailed()
outputEnded()
writableBytes()
close()

关键规则:

  • InputStream.read(...) 可能阻塞,绝不能在 Minecraft client thread 上等待。
  • OutputStream.write(...) 只是把复制后的字节放入本地 bounded queue;本身不等待网络。
  • 每个方向的 buffer 上限都是 1 MiB。
  • 写入长度如果超过当前剩余容量,会抛 IOException("Stream backpressure"),不会部分写入。
  • writableBytes() 只是瞬时容量查询,不会“预留”空间;多个 producer 必须自己串行化“检查 + 写入”。
  • close() 是 half-close output:等已排队字节发完,再发送认证 END;input 仍保持可读。
  • InputStream.close() 会让整个 stream fail / reset,而不是普通 half-close input。
  • 成功结束要求双方完成方向性的认证 EOF;掉 record、错误 tag、RESET、ABORT 或 disconnect 都不能伪装成成功 EOF。

容量、调度与超时

当前代码中的主要边界:

项目 当前值
apiChannelCount 默认 16,范围 1..256
同时活动 stream 最多等于 channel pool 大小
API connections 最多 64
每方向 KryptSocket buffer 1 MiB
单个 raw record 明文 16 KiB
raw record AEAD tag 16 bytes
每 client tick 全局发送预算 4 个 raw records
connection / allocation / stream idle timeout 60 s
control body 最多 30000 bytes
convenience send 最多 1 MiB

活动 stream 的调度会轮转,避免固定让第一个 stream 永久占用每 tick 的 4-record 预算。

API Session 与聊天 Session 的关系

当前 Loader 初始化时为 Mod API 创建独立:

accounts/<uuid>/stream-api/

并使用独立的 SessionService / SessionHandshakeService。

因此:

  • API 可以复用 API 自己已经存在且未过期的 Session;
  • 不会直接拿聊天 /k04m exchange 的 Session record 当 stream Session;
  • API exchange 不应轮换聊天 Session;
  • 身份仍然复用同一套长期 KEM / 签名公钥与 trust state。

API exchange 使用现有签名 KEM handshake machinery,并固定以 ChaCha20-Poly1305 作为该 handshake 的 envelope AEAD 参数;真正 raw data record 固定使用 XChaCha20-Poly1305,与聊天 aeadAlgorithm 配置无关。

Stream 密钥与认证

ChannelCrypto 从 API Session secret 通过 Bouncy Castle HKDF-SHA256 派生不同用途的 32-byte keys,并绑定:

  • session ID
  • stream UUID
  • slot
  • application channel
  • 方向(local → peer / peer → local)
  • data / control purpose

raw data 使用 Bouncy Castle XChaCha20Poly1305。stream UUID + 方向本地 record counter 形成 24-byte nonce;AAD 绑定 krypt04mcg_stream/v1、session / stream / slot / application channel context。

这使跨 stream、跨 slot、跨 application channel、跨方向替换或乱序通常表现为认证失败,而不是被当作另一条合法数据。

Relay 最小合约

当前仓库 不包含服务器 Relay 实现。兼容 Relay 至少需要:

  1. 从真实 Minecraft connection 得到 source player,不能相信客户端声明的 source;
  2. 只解析 krypt04mcg_stream:control,raw data slot 应原样转发;
  3. OPEN 初始使用 slot=-1,Relay 选择双方都注册的空闲 slot;
  4. 给 opener 发送 ASSIGNED,再把 OPEN 用选定 slot 转发给 peer;
  5. peer 验证 OPEN 后发送认证 READY;
  6. READY 前、非分配双方、错误 slot 的 raw data 必须拒绝;
  7. END 是认证 EOF,RESET 是 peer failure,ABORT 是 Relay failure;ABORT 绝不能当作 EOF;
  8. 双向 END、RESET、disconnect 或 timeout 后释放 slot;
  9. 保持 control / data 转发顺序,并对 allocation / traffic 做有界限制。

文件分享与 API

文件分享内部注册 socket receiver:

krypt04mcg_file:stream

FileStreamCodec 在加密 byte stream 内编码:

modified-UTF filename
int fileSize
file bytes
EOF

文件最大 10 MiB。文件名和内容都位于加密 stream 内。内置文件接收从接纳到完整数据及认证 EOF 有固定 2 分钟总时限,任何成功收到的字节都不能续期;这不同于通用 KryptSocket 的 60 秒空闲时限。接收端只有在完整 stream + 认证 EOF 成功后才产生用户确认提示;用户接受时再次检查 identity / trust,然后保存到 received-files。文件不会自动打开或执行。

兼容性提示

依赖旧 0.18/0.19 API 的第三方 Mod 必须修改代码并重新编译。尤其需要删除或替换:

DataTransfer
TransferResult
DELIVERED / REJECTED / TIMEOUT / BACKPRESSURE
krypt04mcg:data
krypt04mcg:file_share

当前 API 的核心契约是 bounded authenticated byte stream,不是消息队列,也不提供 exactly-once、delivery receipt、数据库事务或远端持久化保证。

0.29.0 升级说明

0.28.0 的 HUD 只显示聊天分片进度,不对 KryptSocket 字节流提供进度事件。0.29.0 的 256 KiB / 2048 聊天片段限制不会提高单个 Raw API record 的 16 KiB 明文容量、每方向 1 MiB 缓冲或 control body 30,000 bytes 限制;文件和数据流应用仍须自行定义 framing 与接收上限。参见 升级指南。

Clone this wiki locally