Metadata-Version: 2.4
Name: adp-operator
Version: 1.1.30
Summary: ADP operator management and C2C runtime tools
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: docker
Requires-Dist: fastapi
Requires-Dist: pandas
Requires-Dist: pyarrow
Requires-Dist: pyarmor
Requires-Dist: psutil
Requires-Dist: python-dotenv
Requires-Dist: python-multipart
Requires-Dist: requests
Requires-Dist: uvicorn
Requires-Dist: colorlog
Requires-Dist: watchdog
Requires-Dist: websockets
Dynamic: description
Dynamic: description-content-type
Dynamic: requires-dist
Dynamic: requires-python
Dynamic: summary

# ADP Operator

## C2C 连接首包

C2C TCP 连接建立后，双方必须先交换固定长度的二进制首包。首包校验通过后，连接才允许继续处理 JSON 业务指令。

首包固定为 512 bit，结构如下：

| bit 偏移 | bit 长度 | 字段 | 规则 |
| ---: | ---: | --- | --- |
| 0 | 24 | 固定标识 | 三个 ASCII 字符 `ADP` |
| 24 | 16 | 协议版本 | 16-bit 无符号整数，使用大端序，当前版本为 `7` |
| 40 | 256 | 实例编号 | 当前节点的 `instance_id`，固定32字节 |
| 296 | 8 | 功能控制区 | bit 0–2为功能开关，bit 3–7为保留位 |
| 304 | 208 | 全零保留区 | 必须全部为 `0` |

字符串字段统一使用 UTF-8 编码。字段长度不足时在尾部使用 `0x00` 填充，超过字段长度时按字段长度截断。

### 功能控制区

功能控制区固定为8 bit，每个功能占用一个 bit：

| bit | 功能 | `0` | `1` | 当前默认值 |
| ---: | --- | --- | --- | --- |
| 0 | UDP 主动发现 | 关闭 | 开启 | `1` |
| 1 | 额外 channel 通道 | 不支持 | 支持 | `1` |
| 2 | WebSocket 业务端口 | 关闭 | 开启 | `1` |
| 3–7 | 保留位 | 必须为零 | 不允许 | `0` |

当前默认控制区的二进制内容为：

```text
00000111
```

### 握手流程

1. TCP 客户端连接 C2C 服务端。
2. 客户端发送自己的 512 bit 二进制首包。
3. 服务端读取并校验完整首包。
4. 服务端校验通过后，向客户端返回自己的 512 bit 二进制首包。
5. 客户端校验服务端首包。
6. 双方校验均通过后，才开始发送 JSON 业务指令。

服务端会拒绝以下连接：

- 首包长度不足 512 bit；
- 固定标识不是 `ADP`；
- 目标节点协议版本低于发起连接节点的协议版本；
- `instance_id` 为空；
- 字符串字段的补零格式错误；
- 功能控制区保留位非零；
- 208 bit 全零保留区存在非零数据。

该首包适用于 C2C 直连、反向连接、多跳转发、额外 channel 通道、外部 `C2CSession` 和本地服务探测。未实现该二进制首包的旧节点无法与新协议节点建立连接，因此升级时需要同时更新同一网络中的全部 C2C 节点。

协议版本字段固定为16 bit，使用大端序表示无符号整数，取值范围为 `0`–`65535`。客户端收到服务端首包后比较版本：目标节点版本大于或等于本机版本时允许连接，目标节点版本低于本机版本时拒绝连接。例如本机版本为 `4`、目标版本为 `5` 时允许连接；本机版本为 `5`、目标版本为 `4` 时拒绝连接。

## C2C JSON 指令模型

本节只描述 JSON 控制指令，不包含文件、HTTP Body 或 Operator Channel 接管连接后的二进制数据流。C2C 指令需要同时按照寻址方式、响应模型和传播方式理解。

### 寻址方式

#### 当前节点处理

没有目标节点、也不需要向网络扩散的指令由当前入口节点直接处理，例如 `identify`、`discover`、`shutdown` 和 `close_session`。`discover` 返回当前节点已经掌握的注册表，但不会主动广播一次发现请求。

#### 自动路由的有指向指令

有指向指令使用 `identifier` 指定最终目标的 `node_id` 或 `instance_id`：

```json
{
  "type": "info",
  "identifier": "AdpCpuDispatch"
}
```

目标是当前节点时直接处理；目标不是当前节点时，C2C 优先使用已有路由，没有路由时通过 `route_probe` 寻找一条可达路径，然后转发原始指令。`run`、`call`、`info`、`kill`、`sync_files`、`hidden`、`visible` 和 `channel` 都可以使用这种模式。

#### 指定路径的有指向指令

`route` 不是新的业务指令，而是有指向指令的路由模式：

```json
{
  "type": "call",
  "identifier": "TargetNode",
  "route": ["NextNode", "TargetNode"]
}
```

每一跳按照 `route` 指定的下一节点转发，最终由 `identifier` 对应的节点执行。已经获得明确路径时使用该模式可以避免重复探测。

#### 无指向指令

无指向指令不指定唯一执行节点，但其停止条件和结果处理方式不同：

| 模式 | 指令 | 行为 |
| --- | --- | --- |
| 首个确认 | `probe` | 节点分别判断是否确认，取最先到达的有效 `probe_ack`，其余响应丢弃 |
| 首条路径 | `route_probe` | 在网络中寻找指定身份，返回第一条有效路径 |
| 全量聚合 | `scan` | 在响应窗口内收集所有扫描节点的结果，依次合并并去重 |
| 注册表扇出聚合 | 无 `identifier` 的 `kill` | 向当前已知节点发送终止请求，合并终止数量和错误 |
| 全域广播 | `event` | 所有可达节点处理并继续传播，每一跳只返回传输确认，不收集业务结果 |

`scan` 和 `kill` 都会合并多个结果，但实现不同：`scan` 是具有 TTL 和请求去重的网络传播波；`kill` 根据当前注册表向已知节点扇出有指向请求。

### 响应模型

| 响应模型 | 指令示例 | 响应方式 |
| --- | --- | --- |
| 单目标单响应 | `info`、`identify`、`discover`、`shutdown`、`hidden`、`visible` | 返回一个 ACK 后结束 |
| 单目标多阶段响应 | `run`、`call` | 依次返回接收、日志、结果和结束事件，或错误事件 |
| 首个有效响应 | `probe`、`route_probe` | 任意分支首先确认后结束等待 |
| 多响应合并 | `scan`、全网 `kill` | 收集多个节点的结果后合并 |
| 广播与逐跳确认 | `event` | 每一跳返回 `event_ack`，不返回业务处理结果 |

`run` 的响应可能包含 `run_ack`、`run_start`、`run_running`、`log`、`run_result` 和 `run_end`；`call` 的响应可能包含 `call_ack`、`log`、`call_result` 和 `call_end`。因此它们虽然只执行一个目标，却不是普通的一问一答。

### 全域传播机制

C2C 当前存在三类全域传播机制：

1. `route_probe`：寻找指定身份和第一条可达路径，不执行目标业务。
2. `probe`：携带业务条件寻找任意一个确认节点，第一条确认生效。
3. `event`：向所有可达节点广播业务事件，不等待业务结果。

`scan` 是独立的聚合扫描波：它传播到整个可达网络，但需要在窗口内收集并合并所有有效响应，不采用“首个响应即结束”的规则。

### EventBus 业务事件

EventBus 不维护业务事件类型枚举。节点可以发布任意具有非空 `type` 的事件：

```python
self.c2c.client.event_bus.publish({
    "type": "operators_reloaded",
    "status": "completed",
})
```

C2C 使用固定的顶层 `event` 指令封装业务事件：

```json
{
  "type": "event",
  "event_id": "全局事件编号",
  "node_id": "事件来源节点",
  "sender_node_id": "当前转发节点",
  "created_at": 1793600000.0,
  "expires_at": 1793600060.0,
  "event": {
    "type": "operators_reloaded",
    "status": "completed"
  }
}
```

顶层 `type` 属于 C2C 协议，`event.type` 属于节点业务。即使业务事件名为 `run` 或 `call`，它仍位于 `event` 对象内部，不会被解释成同名控制指令。

EventBus 负责生成 `event_id`、补充来源身份、设置 TTL、逐跳确认、根据 `event_id` 防环和去重，并在直连、反向连接和多跳网络中继续传播。监听器只能观察和处理事件，不能通过返回值阻止传播；单个监听器异常也不会中断网络扩散。

### 固定指令清单

TCP C2C 当前支持27种顶层 JSON 指令：

| 类别 | 指令 |
| --- | --- |
| 路由与组网 | `route_probe`、`probe`、`discover`、`scan`、`get_path`、`connect`、`handshake`、`join`、`takeover` |
| 节点身份与生命周期 | `identify`、`info`、`visible`、`hidden`、`shutdown`、`kill` |
| 算子执行与事件 | `run`、`call`、`event` |
| 文件与会话控制 | `receive_file`、`file_manifest`、`sync_files`、`close_session` |
| 流和自定义通道控制 | `file_stream`、`http_stream`、`channel`、`data`、`reverse_stream_attach` |

这里的 `file_stream`、`http_stream`、`channel` 和 `reverse_stream_attach` 只描述建立或切换数据通道的 JSON 控制阶段，不包含其后的二进制流。JSON `handshake` 是节点业务握手指令，不是连接建立时交换的512 bit 二进制首包。

WebSocket 支持 `scan`、`info`、`visible`、`hidden` 和独有的 `subscribe`，因此 TCP 与 WebSocket 合并后共有28种对外唯一指令。

UDP 使用 `announce`、`heartbeat`、`leave` 和 `renamed` 进行节点发现和状态通知。反向连接内部使用 `reverse_request`、`reverse_response`、`reverse_error` 和 `reverse_stream_open`。它们都不是算子应该直接发送的业务指令。

### 命名空间边界

```text
顶层 type
    C2C 固定 JSON 控制指令

identifier
    最终目标身份

route
    到达最终目标的指定路径

event.type
    节点自定义业务事件

HTTP method + path
    HTTP Gateway 路由

Operator channel
    算子自定义协议
```

当前共有28种对外指令、4种 UDP 发现消息和4种反向连接内部帧，共36种固定协议消息类型；EventBus 业务事件数量不受限制。

## 本机调试网络

设置环境变量后，C2C 节点只加入当前机器上的调试网络：

```bash
export C2C_DEBUG_LOCAL_ONLY=1
```

该模式只改变 UDP 发现：节点公告仅发送到 `127.0.0.1` 的发现端口范围，
同时忽略所有非本机地址发送的 UDP 发现包。因此节点不会自动发现局域网内
其他机器，其他机器也无法通过 UDP 发现该节点。

TCP 和 WebSocket 行为保持不变。需要按指定地址主动回连时，启动 `operators_lib.c2c_bridge_node` 并传入 `--target IP[:PORT]`。

未设置该变量，或变量值不是 `1`、`true`、`yes`、`on` 时，保持原有局域网发现和监听行为。

## C2C 网络的后续方向

以下是设计目标，不代表当前已经具备自动加密或网络加速能力。

### 按连接选择传输方式

同一张 C2C 网络中，不同节点之间的连接可以采用不同方式：

| 连接条件 | 预期方式 |
| --- | --- |
| 两端可直达且允许明文通信 | 直接 C2C TCP |
| 跨网连接必须加密 | 经加密桥接节点连接 |
| 一端无法接受入站连接 | 桥接节点通过 `--target` 建立反向连接 |
| 同时需要加密和反向连接 | 桥接节点经加密通道反向接入 |

直连节点应继续直连。桥接节点负责接入、发现和转发，不要求普通算子了解加密软件，也不为此修改 `run`、`call`、EventBus 或二进制流的业务语义。传输方式属于节点之间的连接属性，不应由算子业务参数在网络中层层透传。

### 加密桥接的候选方案

固定远端入口时，可在服务端运行 `ss-server`，客户端运行 `ss-tunnel`。客户端桥接节点对本地 tunnel TCP 端口指定 `--target`；tunnel 将每条连接加密转发到服务端桥接节点的 C2C 业务端口。`ss-tunnel` 是固定目标端口转发，不是点对点组网或自动选路。若需要动态选择多个远端目标，可另行评估 `ss-local` 与本地 SOCKS5 转发入口。

现有反向连接的数据流会在主连接之外重新建立 TCP 连接，所以首次接入和后续数据连接必须都能重复进入同一本地 tunnel 端口。跨机器 UDP 广播不会随 TCP tunnel 传输；各机器仍在本地发现节点，再由 C2C 的节点同步与多跳路由扩散可达信息。落地前还须解决远端 C2C 业务端口动态分配、tunnel 启动就绪、断线重连、旧路由失效，以及容器内 `127.0.0.1` 所属网络命名空间的问题。

### 路由策略与延迟

C2C 当前以可达性为主进行路由探测，不按 RTT、丢包、吞吐或加密要求持续选择最优路径。添加加密桥接之后，不能假定网络会自动避开仍然可达的明文路径。需要强制加密的连接，必须有明确的传输策略和路由约束；在此之前应通过部署隔离不允许使用的直连路径。

C2C 和 Shadowsocks 都不能凭空降低物理链路的 RTT。桥接、多跳、加解密和额外建流通常会增加开销；只有实际底层路径优于原路径时，绕路才可能降低延迟。未来若要优化延迟，应先测量各连接的 RTT、成功率、吞吐和负载，再在满足安全策略的可用路径中选路，避免为了加速让本可直连的流量绕桥。

实施顺序建议为：先验证固定入口的加密反向桥接和双向业务流；再补齐连接恢复及加密路径约束；最后依据真实链路指标决定是否实现动态选路。性能评估应分别比较直接 TCP、未加密反向连接和经加密 tunnel 的反向连接，覆盖小型 `call`、长任务、文件和 HTTP 二进制流，分别记录延迟、吞吐与两端 CPU。


### 多目录算子检索

`AdpOperatorDispatch` 的 `operator_directory` 支持单个字符串、`Path` 或目录列表，旧配置可继续使用。例如：

```python
dispatch = AdpCpuDispatch(operator_directory=["/opt/operators/local", "/opt/operators/shared"])
```

按列表顺序检索，遇到同名算子使用第一个目录中的配置。上传只写入第一个目录；算子索引也存放在第一个目录下。全部目录都会监听变化。扫描结果的 `operator_directory` 字段表示来源目录，前端支持展示和搜索来源目录及源码路径。


### 算子依赖与冻结

算子可通过 `dependent = ["ADPRedis"]` 声明依赖，名称匹配 C2C 节点的 `operator_name`。默认空列表保持原有行为。

依赖节点未发现、已过期或通告 `dependency_ready = false` 时，节点冻结：不执行新的业务指令，`ActiveOperator` 暂停后续采样。发现、路由、身份查询和生命周期管理继续运行，依赖恢复后自动解冻。保留中转节点以支持跨网段路由，不按依赖列表裁剪网络拓扑。

节点通告包含 `dependent`、`missing_dependencies` 和 `dependency_ready`，便于诊断。冻结业务请求不会返回业务响应，调用方可能收到连接关闭或超时。冻结不会强制中断已经开始的任务。依赖判断基于发现信息及 TTL，不保证依赖服务在实际调用瞬间可用，因此算子仍应处理调用中的连接错误。


### Bridge 网络隔离

普通节点的节点表仅维护本地网络。Bridge 通告远端服务目录（`remote_services`），普通节点可按名称访问这些服务，但下一跳始终是本地 Bridge，不保存远端物理地址和跨网段完整路由。跨越 Bridge 时会移除普通节点提供的路由，由 Bridge 重新选择跨网段路径。

依赖状态与通道发现使用 Bridge 提供的服务目录；本地 Bridge 不可用时，其代理服务也不可用。远端完整拓扑单独保存在面板展示缓存，不参与普通节点邻居选择或拓扑转发。只有 Bridge 互相交换跨网络节点信息。
