From 2e8e05494b9a00b3335202002f73eed1317bd9e9 Mon Sep 17 00:00:00 2001 From: 7iwind <7wind@7iwind.cn> Date: Sun, 26 Jul 2026 01:40:05 +0800 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=E8=B7=AF?= =?UTF-8?q?=E7=94=B1=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CMakeLists.txt | 17 +- DESIGN.md | 512 ++++++++++++++++++++++++++++++++++++++++ TEST_CASES.md | 103 ++++++++ build/example | Bin 0 -> 17336 bytes build/test_datarouter | Bin 0 -> 44104 bytes datarouter.c | 188 +++++++++++++++ datarouter.h | 162 +++++++++++++ datarouter_config.h | 34 +++ example.c | 105 ++++++++ library.c | 7 - library.h | 6 - tests/test_datarouter.c | 511 +++++++++++++++++++++++++++++++++++++++ 12 files changed, 1630 insertions(+), 15 deletions(-) create mode 100644 DESIGN.md create mode 100644 TEST_CASES.md create mode 100755 build/example create mode 100755 build/test_datarouter create mode 100644 datarouter.c create mode 100644 datarouter.h create mode 100644 datarouter_config.h create mode 100644 example.c delete mode 100644 library.c delete mode 100644 library.h create mode 100644 tests/test_datarouter.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 31dd62d..36c1edd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,19 @@ -cmake_minimum_required(VERSION 4.3) +cmake_minimum_required(VERSION 3.10) project(DataRouter C) set(CMAKE_C_STANDARD 11) -add_library(DataRouter STATIC library.c) +add_library(DataRouter STATIC datarouter.c) +target_include_directories(DataRouter PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +if(BUILD_EXAMPLES OR NOT DEFINED BUILD_EXAMPLES) + add_executable(example example.c) + target_link_libraries(example DataRouter) +endif() + +if(BUILD_TESTING OR NOT DEFINED BUILD_TESTING) + enable_testing() + add_executable(test_datarouter tests/test_datarouter.c) + target_link_libraries(test_datarouter DataRouter) + add_test(NAME datarouter COMMAND test_datarouter) +endif() diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..5785df0 --- /dev/null +++ b/DESIGN.md @@ -0,0 +1,512 @@ +# DataRouter 详细设计文档 + +## 1. 设计目标 + +轻量级 C 语言数据路由库。核心思想: + +- **报文类型(Channel)**:编译期通过宏声明的数据类型(如 `sensor` / `status` / `control`) +- **端口(Port)**:运行期创建的连接点,绑定一个报文类型 + 一个物理外设发送 handler +- **路由(Routing)**:将逻辑报文类型连接到实际的物理外设发送函数 + +``` +应用层 send_sensor(port, data) + │ + ▼ + 库校验端口 + │ + ▼ + 调用注册的 handler(data) ← 用户实现:UART / TCP / CAN / 文件发送 + │ + ▼ + 物理外设 +``` + +### 设计原则 + +| 原则 | 说明 | +|------|------| +| 编译期确定类型 | 所有报文类型在 `datarouter_config.h` 中声明,库自动生成代码 | +| 运行期绑定外设 | 端口创建后,用户注册具体的发送 handler(函数指针) | +| 不关心物理外设 | 库不依赖任何外设 API,只调用用户提供的 handler | +| 零动态内存 | 纯静态数组,无 malloc/free | + +--- + +## 2. 整体架构 + +### 2.1 模块依赖 + +```plantuml +@startuml +left to right direction + +rectangle "用户代码" as User { + component "应用逻辑\n调用 datarouter_send_()" as App + component "datarouter_config.h\n声明报文类型" as Cfg + component "外设 handler\nUART / TCP / CAN / 文件" as Handlers + component "接收回调\n处理收到的报文" as Callbacks +} + +database "DataRouter 库" as Lib { + component "datarouter.h\n公共 API + 代码生成" as Hdr + component "datarouter.c\n端口表 + handler 调用" as Impl +} + +App --> Hdr +Cfg --> Hdr +Impl --> Cfg +Handlers --> Impl : 注册为 send_handler +Callbacks --> Impl : 实现 on_recv_ + +@enduml +``` + +### 2.2 文件结构 + +| 文件 | 职责 | +|------|------| +| `datarouter_config.h` | 用户在此声明所有报文类型 | +| `datarouter.h` | 公共 API + X-macro 展开(enum + 发送函数声明 + 回调声明) | +| `datarouter.c` | 发送函数实现 + handler 调用 + 端口管理 | +| `example.c` | 使用示例 | +| `tests/test_datarouter.c` | 31 个单元测试 | + +--- + +## 3. 核心概念 + +### 3.1 报文类型(Channel) + +```plantuml +@startuml +object "DATAROUTER_CHANNEL(sensor, \"Sensor data\")" as C1 +object "DATAROUTER_CHANNEL(status, \"Status report\")" as C2 +object "DATAROUTER_CHANNEL(control, \"Control command\")" as C3 + +note right of C1 + 编译期展开产物: + · DATAROUTER_CHANNEL_sensor = 0 + · int datarouter_send_sensor(port, data, len) + · void datarouter_on_recv_sensor(port, data, len) + · channel_names[0] = "sensor" + · channel_descs[0] = "Sensor data" +end note + +C1 --> C2 +C2 --> C3 +@enduml +``` + +### 3.2 端口与 handler + +```plantuml +@startuml +skinparam objectBorderColor black + +object "端口 A" as PA { + type = sensor + handler = uart_send +} + +object "端口 B" as PB { + type = sensor + handler = tcp_send +} + +object "端口 C" as PC { + type = status + handler = uart_send +} + +object "UART发送函数" as UART +object "TCP发送函数" as TCP + +PA --> UART : 注册 +PB --> TCP : 注册 +PC --> UART : 注册 + +note right of PA + 同一报文类型 sensor + 可路由到不同外设 +end note + +note right of PC + 不同报文类型 status + 可复用同一外设 UART +end note + +@enduml +``` + +--- + +## 4. X-macro 代码生成 + +### 4.1 展开流程 + +```plantuml +@startuml + +== datarouter.h: 3 次展开 == + +-> datarouter_config.h: #include 获得 DATAROUTER_CHANNELS + +datarouter.h -> "第 1 遍": #define DATAROUTER_CHANNEL(id,desc) DATAROUTER_CHANNEL_##id, +note right: 生成 enum 通道枚举 + +datarouter.h -> "第 2 遍": #define DATAROUTER_CHANNEL(id,desc) int datarouter_send_##id(...); +note right: 生成发送触发函数声明 + +datarouter.h -> "第 3 遍": #define DATAROUTER_CHANNEL(id,desc) void datarouter_on_recv_##id(...); +note right: 生成接收回调声明 + +== datarouter.c: 3 次展开 == + +datarouter.c -> "第 4 遍": #define DATAROUTER_CHANNEL(id,desc) DATAROUTER_WEAK void ... +note right: 弱符号回调默认空实现 + +datarouter.c -> "第 5 遍": #define DATAROUTER_CHANNEL(id,desc) int datarouter_send_##id(…) {…} +note right: 发送函数实现 → 调用handler + +datarouter.c -> "第 6 遍": #define DATAROUTER_CHANNEL(id,desc) #id, → channel_names[] +datarouter.c -> "第 7 遍": #define DATAROUTER_CHANNEL(id,desc) desc, → channel_descs[] + +@enduml +``` + +### 4.2 展开示例(3 个通道) + +``` +用户配置: + DATAROUTER_CHANNEL(sensor, "Sensor data") + DATAROUTER_CHANNEL(status, "Status report") + DATAROUTER_CHANNEL(control, "Control command") + +展开产物: + enum: sensor = 0, status = 1, control = 2, CHANNEL_COUNT = 3 + + 发送: int datarouter_send_sensor(uint32_t, const void*, size_t); + int datarouter_send_status(…); + int datarouter_send_control(…); + + 回调: void datarouter_on_recv_sensor(uint32_t, const void*, size_t); + void datarouter_on_recv_status(…); + void datarouter_on_recv_control(…); + + 元数据: names[] = {"sensor", "status", "control"} + descs[] = {"Sensor data", "Status report", "Control command"} +``` + +--- + +## 5. 数据结构 + +### 5.1 核心数据类型 + +```plantuml +@startuml +skinparam classAttributeIconSize 0 + +class "datarouter_channel (enum)" as Chan { + + DATAROUTER_CHANNEL_sensor = 0 + + DATAROUTER_CHANNEL_status = 1 + + DATAROUTER_CHANNEL_control = 2 + + DATAROUTER_CHANNEL_COUNT = 3 +} + +class "datarouter_send_handler_t" as Handler { + + void (*)(uint32_t port_id, const void *data, size_t len) +} + +class "端口表" as Table { + - port_entry_t ports[64] + - uint8_t initialized +} + +class "port_entry_t" as Entry { + - int type + - datarouter_send_handler_t handler + - uint8_t in_use +} + +class "发送函数" as Send { + + int send_sensor(port, data, len) + + int send_status(port, data, len) + + int send_control(port, data, len) +} + +class "接收回调" as Recv { + + void on_recv_sensor(port, data, len) ← 用户实现 + + void on_recv_status(port, data, len) + + void on_recv_control(port, data, len) +} + +Entry --> Chan : type ∈ enum +Entry --> Handler : handler 函数指针 +Table --> Entry : ports[] +Send --> Table : 校验端口 + 调用 handler +Recv --> Send : 被用户从外设收数据时调用 +@enduml +``` + +### 5.2 `port_entry_t` + +```c +typedef struct { + int type; // 报文类型(DATAROUTER_CHANNEL_xxx / -1) + datarouter_send_handler_t handler; // 外设发送函数(NULL 表示未注册) + uint8_t in_use; // 1=已分配,0=空闲 +} port_entry_t; +``` + +### 5.3 端口表 + +```c +#define DATAROUTER_MAX_PORTS 64 // 可由 -D 覆盖 +static port_entry_t ports[64]; // 静态数组,无动态内存 +static uint8_t initialized; // init/reset 标志 +``` + +--- + +## 6. API 参考 + +### 6.1 初始化 + +```c +void datarouter_init(void); // 幂等初始化 +void datarouter_reset(void); // 重置所有状态(测试隔离/热重启) +``` + +### 6.2 端口管理 + +```c +datarouter_port_t datarouter_port_create(void); // 返回端口 ID 或 -1 +void datarouter_port_delete(port); // 删除端口,ID 可复用 +void datarouter_port_set_type(port, channel_id); // 指定报文类型 +int datarouter_port_get_type(port); // 获取报文类型,-1 表示无 +void datarouter_port_set_handler(port, handler); // 注册外设发送 handler +datarouter_send_handler_t datarouter_port_get_handler(port); // 获取 handler +``` + +### 6.3 发送触发函数(每通道 1 个,X-macro 生成) + +```c +int datarouter_send_sensor(uint32_t port_id, const void *data, size_t len); +int datarouter_send_status(uint32_t port_id, const void *data, size_t len); +int datarouter_send_control(uint32_t port_id, const void *data, size_t len); +``` + +返回值: +| 值 | 含义 | +|:--:|------| +| 0 | 成功,handler 已被调用 | +| -1 | 库未初始化 | +| -2 | 端口无效或已删除 | +| -3 | 端口类型与触发的报文类型不匹配 | +| -4 | 端口未注册外设发送 handler | + +### 6.4 接收回调声明(每通道 1 个,用户实现) + +```c +void datarouter_on_recv_sensor(uint32_t port_id, const void *data, size_t len); +void datarouter_on_recv_status(uint32_t port_id, const void *data, size_t len); +void datarouter_on_recv_control(uint32_t port_id, const void *data, size_t len); +``` + +库提供弱符号默认空实现,用户定义同名函数即可覆盖。 + +### 6.5 元数据查询 + +```c +int datarouter_channel_count(void); +const char *datarouter_channel_name(int channel_id); +const char *datarouter_channel_desc(int channel_id); +``` + +--- + +## 7. 核心流程 + +### 7.1 发送流程 + +```plantuml +@startuml +actor "应用" as App +participant "datarouter_send_sensor()" as Send +participant "端口表" as Table +participant "handler" as Handler +participant "外设" as Dev + +App -> Send : send_sensor(port=2, data, len) + +group 校验 + Send -> Send : initialized? + alt 未初始化 + Send --> App : return -1 + end + + Send -> Table : ports[2].in_use? + alt 端口无效 + Send --> App : return -2 + end + + Send -> Table : ports[2].type == sensor? + alt 类型不匹配 + Send --> App : return -3 + end + + Send -> Table : ports[2].handler != NULL? + alt 无 handler + Send --> App : return -4 + end +end + +Send -> Handler : handler(2, data, len) +Handler -> Dev : UART_SendData(data, len) +Handler --> Send : void +Send --> App : return 0 +@enduml +``` + +### 7.2 接收流程 + +```plantuml +@startuml +actor "外设" as Dev +participant "用户分发代码" as Dispatch +participant "datarouter_on_recv_sensor()" as Callback +participant "用户处理逻辑" as Process + +Dev -> Dispatch : 收到数据,识别为 sensor 类型 +Dispatch -> Callback : on_recv_sensor(port_id, data, len) +Callback -> Process : 解析数据、更新状态等 +Process --> Callback : void +Callback --> Dispatch : void +@enduml +``` + +### 7.3 端口生命周期 + +```plantuml +@startuml +state "空闲\nin_use=0" as Idle +state "已创建\nin_use=1\ntype=-1\nhandler=NULL" as Created +state "已配置\nin_use=1\ntype=sensor\nhandler=..." as Config + +[*] --> Idle : datarouter_reset() + +Idle --> Created : datarouter_port_create() + +Created --> Config : set_type + set_handler +Config --> Config : set_type / set_handler\n可任意切换 +Config --> Created : set_type(-1) / set_handler(NULL)\n清除能力 + +Created --> Idle : datarouter_port_delete() +Config --> Idle : datarouter_port_delete() +Idle --> Created : 复用 ID + +@enduml +``` + +--- + +## 8. 路由示例 + +### 场景:传感器数据同时路由到 UART 和 TCP + +``` +应用 + │ + ├─ send_sensor(sensor_uart_port, data, len) + │ │ + │ ▼ + │ handler = uart_send → UART 外设 + │ + └─ send_sensor(sensor_tcp_port, data, len) + │ + ▼ + handler = tcp_send → TCP 外设 +``` + +### 场景:运行时切换路由 + +``` +端口 sensor_tcp 原 handler = tcp_send + +datarouter_port_set_handler(sensor_tcp, file_send); + +端口 sensor_tcp 现 handler = file_send → 改为写入文件 +``` + +--- + +## 9. 弱符号机制 + +```plantuml +@startuml +package "libDataRouter.a" { + component "弱符号\nvoid on_recv_sensor(...){}" as Weak +} +package "用户代码" { + component "强符号\nvoid on_recv_sensor(...){...}" as Strong +} +package "最终链接产物" { + component "使用用户版本" as Result +} +Strong --> Result +Weak ..> Result : 后备 +@enduml +``` + +- 库为每个回调提供 `__attribute__((weak))` 空实现 +- 用户定义同名函数自动覆盖 +- 未定义的回调在调用时静默空转 + +--- + +## 10. 错误码体系 + +| 返回值 | 含义 | +|:------:|------| +| 0 | 成功 | +| -1 | 库未初始化 | +| -2 | 端口无效(越界/未创建/已删除) | +| -3 | 端口类型不匹配 | +| -4 | 端口未注册发送 handler | + +--- + +## 11. 配置扩展 + +### 增加报文类型 + +编辑 `datarouter_config.h`: + +```c +#define DATAROUTER_CHANNELS \ + DATAROUTER_CHANNEL(sensor, "Sensor data") \ + DATAROUTER_CHANNEL(status, "Status report") \ + DATAROUTER_CHANNEL(control, "Control command") \ + DATAROUTER_CHANNEL(debug, "Debug message") /* 新增 */ +``` + +重新编译后自动生成:枚举、发送函数、回调声明、元数据数组。 + +### 修改最大端口数 + +```bash +cmake -DCMAKE_C_FLAGS="-DDATAROUTER_MAX_PORTS=128" ... +``` + +--- + +## 12. 约束 + +| 约束 | 说明 | +|------|------| +| 报文类型 | 编译期固定,修改需重新编译 | +| 最大端口数 | 默认 64,可由 `-D` 覆盖 | +| 线程安全 | 不支持(无锁),多线程需外部加锁 | +| 弱符号 | GCC/Clang 原生支持 | +| 内存分配 | 纯静态,无 malloc/free | diff --git a/TEST_CASES.md b/TEST_CASES.md new file mode 100644 index 0000000..9b86942 --- /dev/null +++ b/TEST_CASES.md @@ -0,0 +1,103 @@ +# DataRouter 单元测试用例文档 + +## 测试环境 + +| 项目 | 内容 | +|------|------| +| 语言标准 | C11 | +| 编译器 | GCC 11.4.0 | +| 测试框架 | 自定义轻量框架(宏 + goto cleanup) | +| 构建系统 | CMake ≥ 3.10 | +| 报文类型 | `sensor` / `status` / `control` 三个通道 | + +## 运行方式 + +```bash +cmake -S . -B build +cmake --build build +ctest --test-dir build +# 或直接运行: +./build/test_datarouter +``` + +## 测试用例列表 + +### 1. 未初始化(1 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 1 | `error_uninit` | 未初始化时不能发送 | ① 不调 init 直接 `send_sensor(0, ...)` | 返回 -1 | + +### 2. 端口管理(7 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 3 | `port_create_basic` | 基本创建 | ① `datarouter_port_create()` | 返回 ≥ 0 | +| 4 | `port_create_multiple` | 多端口唯一 ID | ① 连续创建 3 个端口 | ID 互不相同 | +| 5 | `port_create_reuse` | 删除后 ID 复用 | ① 创建 A ② 删除 A ③ 再创建 | 新 ID == A ID | +| 6 | `port_delete_invalid` | 删除无效 ID | ① `delete(-1)` ② `delete(9999)` | 不崩溃 | +| 7 | `port_delete_twice` | 重复删除 | ① 创建 ② 删除 ③ 再次删除 | 不崩溃 | +| 8 | `port_default_state` | 默认状态 | ① 创建端口 ② `get_type()` ③ `get_handler()` | type == -1, handler == NULL | +| 9 | `port_create_max` | 满容量边界 | ① 循环创建 64 个 ② 再创建一个 | 64 个成功,第 65 个返回 INVALID | + +### 3. 类型与 handler 设置获取(7 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 10 | `type_set_get` | 设置并获取类型 | ① `set_type(sensor)` ② `get_type()` | 返回 sensor | +| 11 | `type_clear` | 清除类型 | ① `set_type(sensor)` ② `set_type(-1)` ③ `get_type()` | 返回 -1 | +| 12 | `type_switch` | 运行时切换类型 | ① `set_type(sensor)` ② `set_type(status)` ③ `get_type()` | 返回 status | +| 13 | `type_from_invalid_port` | 无效端口查询类型 | ① `get_type(-1)` ② `get_type(999)` | 均返回 -1 | +| 14 | `handler_set_get` | 设置并获取 handler | ① `set_handler(f)` ② `get_handler()` | 返回 f | +| 15 | `handler_clear` | 清除 handler | ① `set_handler(f)` ② `set_handler(NULL)` ③ `get_handler()` | 返回 NULL | +| 16 | `handler_from_invalid_port` | 无效端口查询 handler | ① `get_handler(-1)` ② `get_handler(999)` | 均返回 NULL | + +### 4. 通道元数据查询(4 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 17 | `channel_count` | 通道计数 | ① `channel_count()` | 3 | +| 18 | `channel_names` | 通道名称 | ① 依次 `channel_name(0/1/2)` | "sensor" / "status" / "control" | +| 19 | `channel_descs` | 通道描述 | ① 依次 `channel_desc(0/1/2)` | "Sensor data" / "Status report" / "Control command" | +| 20 | `channel_invalid_id` | 无效 ID | ① `channel_name(-1/99)` ② `channel_desc(-1/99)` | 均返回 NULL | + +### 5. 发送触发(9 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 21 | `send_sensor` | sensor 类型发送 | ① 创建端口 ② set_type(sensor) + set_handler(test) ③ `send_sensor(port, "hello", 5)` | 返回 0;handler 被调用,参数正确 | +| 22 | `send_status` | status 类型发送 | 同上,用 status 通道 | 返回 0 | +| 23 | `send_control` | control 类型发送 | 同上,用 control 通道 | 返回 0 | +| 24 | `send_type_mismatch` | 类型不匹配 | ① set_type(sensor) ② `send_status(port, ...)` | 返回 -3;handler 不被调用 | +| 25 | `send_no_handler` | 未注册 handler | ① set_type(sensor) ② 不设 handler ③ `send_sensor(port, ...)` | 返回 -4 | +| 26 | `send_same_type_multiple_ports` | 同类型多端口 | ① 两端口均设 sensor + handler ② 依次发送 | 各调一次 handler | +| 27 | `send_switch_handler_runtime` | 运行时切换 handler | ① 注册 h1 发送 ② 改注册 h2 发送 | 第一次用 h1,第二次用 h2 | +| 28 | `send_zero_length` | 零长度数据 | ① 正常端口 ② `send_sensor(port, "x", 0)` | 返回 0;handler 被调用,len=0 | +| 29 | `send_null_data` | NULL 数据 | ① 正常端口 ② `send_sensor(port, NULL, 0)` | 返回 0;handler 被调用 | + +### 6. 接收回调(1 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 30 | `recv_callback_user_impl` | 用户实现接收回调 | ① `on_recv_sensor(port, data, len)` ② `on_recv_status(port, data, len)` | sensor 回调触发,status 不触发;再触发 status 回调,sensor 不变 | + +### 7. 错误处理(2 个) + +| # | 测试名 | 描述 | 步骤 | 预期 | +|---|--------|------|------|------| +| 30 | `error_invalid_port` | 无效端口 ID | ① `send_sensor(999, ...)` ② `send_sensor(-1, ...)` | 均返回 -2 | +| 31 | `error_deleted_port` | 已删除端口 | ① 创建设好类型 + handler ② 删除 ③ `send_sensor(原ID, ...)` | 返回 -2 | + +## 覆盖矩阵 + +| 功能模块 | 用例数 | 主要验证点 | +|----------|:------:|------------| +| 未初始化 | 1 | 未 init 调用 send → -1 | +| 端口生命周期 | 7 | 创建/多创建/ID复用/删除异常/满容量 | +| 类型与 handler | 7 | set/get/clear/switch/无效端口 | +| 通道元数据 | 4 | count/names/descs/boundary | +| 发送触发 | 9 | 三通道/类型不匹配/无handler/多端口/切换handler/零长/NULL | +| 接收回调 | 1 | 用户实现覆盖弱符号 | +| 错误处理 | 2 | 无效端口/已删除端口 | + +**总计:31 个测试用例** diff --git a/build/example b/build/example new file mode 100755 index 0000000000000000000000000000000000000000..4be9e8b3876580d6255a70ad7ebc3aba007f1fc1 GIT binary patch literal 17336 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 V#Z|{o#q|HZK}lTYQSem7{{UHX=j;Fg literal 0 HcmV?d00001 diff --git a/build/test_datarouter b/build/test_datarouter new file mode 100755 index 0000000000000000000000000000000000000000..5cdbb91e7872de2a4c25cc97036cc15675225121 GIT binary patch literal 44104 zcmeHwd3;n=()LZ@LfB*xltrOOMFAn~2q?-HXfQ0YC<@qgl7>XH(cNKDK{PJJ21Vr+ zMnQuEh)z^cHlwl#(x|8~qT)Czqbwak#EprH^!rqurO#~>AoKg)zdmk$xuf4W zs&npky8G^w)Io^}36|QbS=U;8)KGBx%8I$m6~eOmT0O0L*tfD;S~b9~j{m;0I=@G+Frh> zx8lmn8VSBCO*!(-g88C;!BS_rQTk;@eZg09#3s37DOW7zRC!!FLY2y&O&@J~54c=%Wg zYPJwhy3tCUBaW3ms%tFk3IE*1Yo;DrbKA087CwH*_!e^?9P|6cRKifZNro`Akv`c( zJ|?;j{s|-eaS!u}N829X2RVYOc;6jtnX(Q5*{z5_2YeU)<2TBqitv3Z;SaBb-?kF| zC6(}(fbYV8{PuS+T=7gCC*YrS{b}=f(r0)*le2QY*;#k^ERP2PPxl0ZUVqS&qXWs6a?9Ixy^8H!4!O0e>pcLt04Ne`> ze}Jb;ayLzNjgeiFyIYE&IanLv`s~*h|9?4t#=o3hvYF3s!E@+-ri?juCOq-go z)I70Yo(~qw`AG4LB|oW{#Vo~F#|`56`=$w8(>VTDlJAP+ACmk|ar|SF-!qQ?o8iQXOCEx`~EG##OQ8!;r66$?2u@8KiEG;TL)0oZ)FialWKk}`*$=NUBJh5 zLGxqT9(_y~RzH?))yH%}^<&u@eM}cRKb9@i$8>@7W7(tX7}h4TwSgAf!|@*vll9o~ zFLB4;#T_4vJN_c>_+#_9^LImv_T1_&`r2K1;P}WfDV?`;e#bq(PmiIN6>ah=kUvjO zuHWiDG!hP)-=`t@X}(>XieBZxCUD&o0FmozMfcZlHHUE9WC{Ggsd9eze^Sx*l)LD- zd+V|5+*`}5xf6D{_nZngh60~dkQCiNnfs~a$-i^@tV6X`aOGHcVW0j)x{D45>$*dI z2H_y`&Z%fLk^$%Ku;=4AVIpL;eUdMmj!GiNLau1mNq14fad*+!h-i0VdHuUvhMI&R z8cI3oPXC8{e%=3}F1Y76nGI%9^@brk2SnkPgd#hGV0M~1ycxuTiQ6hv-b;Zxod(oc zf$E$F^!ox={N-6KW#XUb|bt%$NibCco$Uwy#69{7Wfu^wC5qsK7X<(qTge!M! zCSGJM(yAz>q;OVAG&l;OuT8C)M8KjQz~0N)@J7ZaLwP8rge&eaE1IBUGu?MYK0g_a zhV3`!iQ9@J2@Aq0t4Q`p<50>f_u|mN`&Xlt2ZqCgDXS7fDaU)Ith(z!$bJM8$k>h{ z`yNmn+ZiCxpqW&#$rh%`XJP0_hmbv7!BYf$sD}S%JPFFC2LKP*7b|dM2B(x~xUUrc z4BC;tfV`t~=d8Tl0U~hHVzy*|7$R?P#h$iE@-Xu3D)fxo?igLQn~>ay-hNkzxMZ-=dUpDYY6p3Kx%T{R zXjEa!a^d;qKmufLJ! z|E9oCGT7<)9e_AIpMS2(Js5l1B5SWF&o8df^NWS&7Z;^0iOgeM zafP^Ifh#UbSsuw?+*w3&i8Z5c;jHD>j2akE*34muYH1OonvYfqPj=hZ1C1*Ci;DW9s9e~hQbI^*vV$_JRrbF}iV zth|X*PUCbT`Hr$>q0W|T(DA~Qg(6!PQnnnPtFq-YP#pfDXEl6;34agpkX@j_*$j4O%VI#Pm@W511lf|xmh3B_G;BA+p0>yXLnvF$m<4b< zX0CQaAu)y}&X^A(P{FZq&e{hNs2Q*(Jkh=WlQ!nC=J`0*weNdmk-g% zGB>FDS0EaTK!sCEsEV9XqMb1t6@@7!!WkvxjJ8lAoY4XlJEIW@^zM8rxOeODoKg>> zYtRmIM&y18{|W@)2dH59qb9r_;30d10>8vyr!)TfOO>4wh6tRohb`GzP#U(!V^3RT z=b*DX%g|{%9LXL`S5-u%F?GB!l2DX#Je*R&nW8Tc&NIakTyqNVeF2KSw-*H5@i7(b zj_G5Fk|*6$KMU`@py0~{e5HnuHsK!vj`O_&=QG&py(NG+#{QX8sR$zQ-U_y4UkRmQ z`(o_bdk>{l!F$Kx>caA9kY?U*3NfHDd&+K)A8mBIrm#53?AuR2se$|&sIdJMCgc?G^D)pJQToyvqqEmL&~gwdHlOx|Lx`c_umSE;5+eK~d0c?mEDeRc-65hO%l ztApae{(dj)hwPJ7h(SNf=wWLZqG7QI)ePS$;O}er9VYz5j}+Kq1zyBpXJEGhQpLco zh6sF7hkap>g3_=(5PRCfDaE1?{qGc6Hh=;xE_2&ec)~BC6MkVzaRT`UC-LiMzl)2g z!db=F-Az|#Ki-3=(2a<2$89$l?zn*Zmr~4rnrC+Wj~`Ujw}A@VE1-mpsuaT?#?;@_JM4O071V)ecu`E4kb*AX|>hZ5JdbO)5Y_^Le%7Xfdr~se z=weqGqKJPrN5Ve=0r=-sFnq2FuMT*~eo28>FxVOKJ;$pY@l6ncFTCs@dlHm}?NQj% z7TMhIY=<)6XJHO}zQ39c8^b|z^wpk&SS*7=(W`q$7*0qQ_K%r8Z4RC|L+}5dLj2a< z#u>VT(N~6MI!E6^NQhWG0E%NV3j~}yn+kTW%WTGTFhsE!t>8lie1wLd#Ql|?g=YXB zvM*8KW(;=5A_b5tj=nAsK`f4Q!|c7YDHc1hr!CU8Z&hP)G2DWga1))|5t{SFA*h+; zFFEPvLVxKh7!Xc5gnJp?%g2s!du}%!H5)t;n}MLh_T`XIfOus$N5jx;XKc!6sn~oA zievL{5Qxp!RB&wWGn-KdhA1{06#OLte^tXLnecyoN3nT8foCz;8Jp(;sbXxNfCyrf z%~@dggwn9x4tv@ni>|F|Z0@;E#D-jli1@`Js4qYjmM8F8>mPR^HZKDqoH8`kh|P(k z+@6Tp^RK}Zu~`KwY(E6~)5T^Dn(d6u1W1T+GaMAhCItjyGl&X~O$0YEIz{qeh+@-1 z!5azqg&Mxyg!ctJWPf|7iaQ8*$Qhe@fK)Lyza3#;e8>K=mq2OQei(b&HdFRT9;BWk z55>v6=w1s65vP@)I8IAJpm$GG!Ex$qwjlhi zh*LnpZxir54Zp~QF9tkh_gCOv40guJ2S^p;GzuaZLpO0o*gwytI32>Cw#ewK&vp#q zsbxd>&0X{f#>-{!jXcf#4XR4tgf7wl7PxQIfen|j>7!;RUj~FZw_?oR64xuGe-NvYnrB(|WpL*&Lp54$HS8 zA;P@@6o>mI5NOxSRIte#%qH!Nh;ZMh;CBl691ZVa!k-5m=RO4<%3x=>rvXyMaHm5A zzL>{xux%&}+y9Y^G$I+jsu1qG;WrWP?(hvfF%n&03st4tbV%Cc-b#mL-(fa=xtRjn z!4u(r2vpe4hCGF#tOxdpAJXa?ZRh>o+DEFACK_cZuM&Q`w!5t8?yOY}cypxJ+6`VlisHh3a^-#b9zei8B2!Omd63rH1% zy#XTdMSo5!`(`K&+t*`HTV!LmDg^r@@+!xn2)>bXRT5N{?tw1Rp*grqiuGTZzQYVn zQ}9F>zWkbky~Qu*s*4lV9I`;$`Af`BF>s~-&~}=dcD6$ky4cjTvl={MXByiXr|hiL zbK)><=e6#-KRwCLN7~Nnenx*L2s`V|pt-f3^VrVusj@%Aw4K`8&J8g;yMZg+tL?Z= zJ3r&?H2U+5X=gupqCYD^h3&b@&Ozu9liYM39o2bSoJ#6H*6Pdp8r}FGC=e1Ukl;Lg z&2(phHgz?dx>%YzGE0odTyg{F!97Ty!tztWbR=~YuPsXVX^YcMiVI!uB#~g(EAy3ESBvev}3vAx5bW6pzvgAke~zRIte?ZbfvKJ^({B zO1mg{TLDkj@Xt;7O@N2&1O+b7Bi+tXdKn;99Hk8*0$;q(X<)w!rFi~>J#CRjovScP zJHXc>I^V!IB4XES-iMkuLi1kNybCn1C88sz^UKkU(to2J!go*O&OrNa=vUnA)@Jth z08eD<$uB8{U*^gX%8Sq47zBmoiJYeIqOSC7t-HUe`xvA}nm!E)j@ypDhV2hfFYGL2 zJJXb%N+$HBo#Io)4GEDdy+LuRbOwP`=|TmY+>0v*-QDppM5$6!!B6EdyqbouHsS55 z2lgkxu`awkzo>Gm9D)c^WiKbCJr_#D_8r*M7CD?;g;ZIA_K8&KNW%yNnXcA) zKv(HuC=(8N=^DcUw+P!)%>gk8JQ0Vwpu+aC?9&g~CLQDBumut#4zGY>Up)^3ZFzwT zjzg;1jF0{$;&8WuPZRJvHN1@pUk*6l{VQ+^gPn1h3P=@)?5z+%9Hw&4+7^_C?H_OF zFjx^!hbqM34)Q8bsGZ;&F=RuUcah-Xj_^`3P(jTDC5PvOFj@M&4p$VH-8x_Y{y8`C zUuIG+0#Af%BB-!^9pq0p!%jv|oYz4OAtAz54HSp#x7#>ezf-~CT52}qG8m#^vm4b6 z4-5D%4WDVkfB7ec>lp=pg2B#ky#+`W!}T&m5U#H|G3*!t7Zmb8OE|RbVz^a2P5FYR9;x|L zRG0n?xX?Vp)cpHr4$VjDkTt&oDr_%QnzxboeET4Esc462{V(l>4SEn1ARE7E8%J=O z*R5aVv@uuP7|u32NgGGfVBkJ!fFB9f!PKIjG&LdvC$xcwOancf2CmQszS~Eg_%PGR zw-Ts`dk=e}9n3f$2`z(!$hXHqaRNOA0{sk8!THv~Y{6O>qI{dA;5P~Q1P!lc!tVz> zWOr2H%Ngvn(%IbhwLvWOWhxXh`&Ti0HlfzJ@yIvLJSyt0hEUAd$FgDhhyX< zrYd*QTkv{M9#uQsMW05(5JP8aP)`85G!ecMUEGVy3f;vyfEj~&Djo9By9yN6e?XL+ zT=pqhBP#N+wzh|}iLKo%tkEDm=7Y6r+S+8(S|g{mYTDX6ACtAeLzHYB$=;kurY_Z{ za$=^A0g8^1sX{VUU7PwH7jL>_&jDt1Y%v{j$8Kd)eU&M?YN{Z6rd$!9J&hnCvZp#I z&Ys^hU=Z(rso?B++H65<7^3XigKCEF6!7;o{0vn*uyZ(W1Eh-Cvl=2u zjP5+T>`_pP_rKWFCbH+mmDGC5o|X*acosoSW=~6?OOv#dpXh2hxj$frle^L(JNeg- zD4w6E|0#RsYHM%s&|zyWgf$B3YKWB9($?CT*7icFVeM@?WNRU|mLsjH?73K*^2AK7 z22^HG3Yn^{O}&Y03!Ob#PE)sNQ!X}@s7zIoJ#AaZXV2a=l|4H^aXP#S0zKYD1!vDB zvjvC$A+qOj1%F7uAJOn@O!!8?vBp<{Z)UJFd+rCMirM3b2oj?sk72tpl!omZ*mL&q z#lsxTvb2ht=9!R0&7XjiJ`O2?`9&FVi7wE?~_!K@@^k*~{38ONQ!FGv!-f(%e96-iRv}tvSP~ zd92plf;Im!NzPW|M1x;!16i3{WvDA{sMUH+we_58PwiF>-VQ2kuT*L)$*uP#I1tA!sndvG8PZsd58onPVYVC&!fQRfF3jBwMbUSma zEg)6QtqUN6+&aWrY;T0pu>At|v_&rb%h}#XY$7~v4HDoMaSO476!UGxXW&Iv!QH5k z>$>ah#pRs4=mSW6brHLbu0?voZfQ#$u2&c%*Js`OA-8jcIogJTCz7lIDBS}iWsYgN^p_zn^xC+brWIVawBDV|I7)@WX5&07jyB%5Ls z%bmrz*ATh$x8^!`PEA0J+vyljV~lj_CGT^8&Nm0&dEklM*}IEkyB_k8wvHfoN8~VrX0?pZoi>mVxzijJ=T1Wq zXj3C9*yLJ1xyo){4MUVWhsR6!mmmQDh6;u+FyVE%_3IV*MFu$>7&_RnRtr!t-Cqb5e{8*(*RJ95hcoMryljwY5#P>mi8#w=&f} ztJP+*+M!BqCAlyaZgS2e7YXAyx10xxbHN6Iw$z}4gS(2(r)oKCM;M}9`21!G{|5-b zKc#}<_nB}Da6Io);AISU=ED2$R5=$)Ap&1q$upKc0ZPO6aO^o3UT=m2FgNXEU1Xw*t`ASZw)25WdH#r=86bKENl&~CKd z8K65MAqHy>C=O5t2zcB_1$(@v*^Gx_hyv7G!8;3h4-NkY_dYtF9>7C(Ed{n1>@qEO&)1sa6VZ%P!0Pw?Rqyw({{-Qd$oE&>Ay@CIw%EzF~32wgRA z5O@*F1Xxp-By05)lG>PqNf*=4-H1%0muVt|tu8lW^4<<)4K;Nm?ILf`VQr%B1%=N) zK$KcumW(|brR)sXc4{@zcCI5k!?m3axO365Pj}iGqwO?jJ156VJ3C;^7#a~C;4~$M zg&n%k{uJQS5!%iO)6Va2JNk2!4!J+CfWq@XX@|~`i$SY1&E&H8D62E_@MmsYi#kHACebx@Q|X=5Zj{$Lq|QY8~oC+S~~$Y)=FVTCB3iu@j9- z(v9klIUzD1heo4b!|j4-)b<9{sP<5xuH>77F7@d~Enui@)HU3wQ=?>~x|@x1=|&xT ziyE~PC~8z?t82e+Ou1%W$v+7Rk!!SSo+tQ15OCCeRInc|H=FY!3{kG#tl%RBe5{5i zn(*0xhwL^A+={`@T)Q5SD&|^Gh#=Sc^0a0D^9IVbUD(qW>2-cpbL~>NMT9T~4i&lf zyXM`fd4~lLd38)=qMG}@aH-fv&ZDS`({+Ees5DPUwUj|Js*|^J4<0ios!`yH6l)49 zZ2va$^wInaI_iw(E=Y)IZUV*8Tn7TtTu%i@bDY_XueOM2h7|l>0e?`#yP5FSfQRf` z6!-=PJEJ)ZkXSSwpDicd+@_fzj89cNKwbqJ=LF%yz)cOHJ2W=p4h=g_A5iC!{tzA* zEr=OD6MW8};AfuU-ogVOWZem#GVF{Ilyx^^Pg~etE-9?31yy7(#J;pTsvxq5MdZi5 z+rczGUx{iO{xl~wBj`OkQ69yOfIdt1fl;g{C6%qQ1(||2g=j_iO#5 zP5sd~sWr8f{@YCb-=I$P?RijPJEZi}iMoV>>ijI@EA`PQ$2+u%kPyRd1Sn4JK_KAo z!Bnt2%W%`9yPFR~G&O&kWhG0?SFJDAidjh_D-UZcb4)AkomMW^R=#`ipPW%V zS`TI%QCtKG5yc`<9K|^x(Aj&a;3!^dw%`RAqA1>|;KKxbl!ixf9j@JSC*UEwwE|zn zVCSUi2BeBn><$sk2ix!v3Q_!YC`Iud>}iYis9V)jpbKshr@(`xnD08u1rMjY4bW#? z${fQjikNkOtto;=j}Jz}dg&87Xzwvdp6;fXa%&$pvpXF;ksobAh3z_!=lrNe_fnOA zDz+UO<2>zsJVZq>42mPT2?W}-nF@}e*KAJYH4(vu3Qix|27G~r_c7tG0Uol`75Ekg zJ0ti2AXSWD03wKBW1frcCQur-Yhuq44A!n{1aGaQB6uk|n+$hrF3b-nAf_yL2LEUV1!F38eMwu`lCQDz^VY6`? zro*;oy(1Hzr9;kyOi*EaC`45z3QK5{k8ri z*8gqF=`*1roaD@eHy|N0;U!R<2`fOLn=7eclWwy)AFdObaHoR%1$?@OUuMFK0T0;2N?Fr)C5E+OH#G4-Nz#Eb7IE>ta9EdzZyM{Huo^MZ*wHH$DHB|8x z!WZd6)CdgoNE@O8I0r^r64e}6N|DAyU5snPNNu8Sz!iDKBFcr^CK0d=YjJqcMmns* z4u)z=LoZRqM{sxKAZ~ji?+`TwH?onFd7>IJ-li3Ew7kN0Si+lXqr7rRMB4gCbW8;RQaDR!wu<$R_LRTG9D zl7_02p|%7m{T#biL?wKIT@#|Ze}!EgqO!33RQo%MHMk3_cXTQPjOQK2+ zVz-y5FYytY$h$=CFT-vNQPYoLx1Ol4k6~9r)W9FGdzPr^uh=ans`^P~a)dD1P?{V` zCJP8s_>+RP7mx%A=|GTC1i9h_c5b4+`HuoeU*Rr9#QYqw01nmrM}G|UCOc`*>%KXy z51LbWImGZB9Ekh3)PH76{q%R>!x248Vl5JcfuBT!fyB0{nf?Fko9O+{q@xoS>hB)ybXzp zvA0q$t0!Vm*WUOCL7%J?I$l8wYHmb8qDTDUl#@VPNBZ;En(cI0pf>Gv&u>hoX+ZqZ z&$3cs2{)SiaHF|Dl!B(?tl2NRyD;Tsf;4NbErC!aUcv~Ydk-YfC8ZWF@%S{3ob3k@I+rwinZEk3 z3m^%25*QomJM5O=Sq_WLV(xLBzKbBDm7G1QtP=hL_>oOSZL`VV^`#&b`s@0jM$ zr&ipjU`N2L8SM#t_vMx9Zz&pS9jU&+;YO$`Dkz7xJ-AxGZ%gn-);5~84Xae!?mo&d z!8R3ww&Gd$-2h?n3%+5o0;90#VT%~5X+OU#yRf>&X!%1hl zjpuckK7gzB*mQM60#8@g3|K#U-QBQ$wBkU1290_|Qme}@fM})gm7)Q72{XI)Mm`rD zjwPO+xZ{k)&)xl?1mDt_==esdtD}n3; zq^NFUrS+?XinD0ft=z80P=(0>Pk~`@r@Lq;2Se&UxUR2hMrmoCnT%;Q!JC^xILi`TYJozo#HKD>o}>Wmqm( z>ws%Ke)`PkYMn8$x6AU)$oHiOeHpF}o!h&nh z{XTEd=SlMhveGSa;PU%|1^!&9fT#(|Daa0H<>Pm+0(m(;S61#c{32I|HDqAG70Aph z$j)%3`CMq&)B>MF%--?en|R^9pi?O*aJoRsI_?{KkH%Xr^~E ze)h}dn~~`)2n4h8a@nQ_KQ-o=f}b1X2IOP~a=iG(F;SJC?ZZ!x#ax0$5y}&oo<*2g z1$ChyASx&O^Kx|8Wgk4=j0{g+uFpd0qMtAGWmw#;J_wA7(SaeLm*Hu=Pj|pF3i7kF z(#dU;v;2Xe3usI8l&eJWT^+hQM9E*IJR|0x4qaSrCKqI9x7E&5P4fG4@}~JLQMl4X zo0Wd#NS8_^Jh^#6&t$~SVMVt?`1Q|mRc0M7#LvIw=K8Wd439OstEDP(y*a*s74YQ- z@(?T$iS)eOpg%8LV={b!^nf**F|G`6&})rmAVvsfr!_#}k!v|QNINYjQ`m#i0RtRE zk8C9~R4^samF~?(Y%w5Q!7SOT3=aoYi3N-C2qK&LpM1pMN0U6>C-&q!!-QXpT%*4IO6I`PwP#SmvQgxWD zs9RHf8H^G$tBM^5Sg^#zl;L3(H|!3dKhKlx%bgO;v}ULTjfGJ<6@$aWt)Y3l4>s38 z}iJlS(0vAk1!IlkQBL~Fd5?tHvG-Ya^-si2)jI>$&3za z*Ctn0Fak%P^_WX&QSEB_4N|_89;UUvC|idikMbTYIiHL2fAFGj z70NyM6w6MOU*Ho?5tOs>DY>MYu!~oHZBTmfde4or!)K63ISS=ml+#hJLb(#nP`eOucmlabjPn`{I;b2#KZ<(y zY3l2u-@{H*-yZd&P>)A(G5b?v_ES-xjrv@ax_#Ar351~jWrJV(*J$((NBvY?KM(xI z7<0)On=$#;iAuf%^_O5Qu5rk(((=2&e-&f2o}<3Hu0INX@iWorwT}Aky1p*#ZYqvO zKXcUY*7faCKOJNKM@M~`u1`h%kmb=R^&{5)LS3JY`gNEimN@Dk*Y)#I-}$-9>q}7o zBIcA;4*6%a{4UgY$6WKhqyCVtKZ^S5n1lF>7F7OJTd)0F7jxR*@WSakhx`F8-yZe( ztE17CC}aIwT1~|#75o#J!>)v#Sp6khe>Un9)?-XM^xv)Z&qICIhG>))rNrb{>iQDY zmv0nve5`(o*1rq&v}TXqg2d|GTK*{NXJS#twT||6*Yb5SCojU>T3LO2)UU&`jR_9< z(ORB<82Sgy#f=^Hb##3;>JMU$rt?*-eO-0^Jk%#(?yl>oucqruQ2!lPcr0`DZ?W#* zF4X6~8;usApE3Q}IzC5HzYXVrY={1It-mhLDfM?pqaQf*zpeGRNBx^PXH+)-q@w<< z)6{39K8*T~Xr6B0n(w11Os|1Q+e!?|pSqy2B{_8&!kmrtV6 zw;c7a>H4}jC+@{Lu)afo4XwXD>bK$CSKCpapzBjn|KjJ-=;Mz1hje{5>RmWjV)0h2 z|HF*_qyB=^)R&;X?rG|Gp*|7ytsVWlNcZn3>T7<5^O>XmVqISs=k$6whgVkL9`)yA z-A+%l{duwWr=tE!)L&`Vr^V{CQQrggd(8U8oebdfALq}7I2-g+` zj{0m}zYFzKP(R&KpR4PSqJ9(AJNlm+>p#x3O>oWfq@(^(UEdz{k6 zaLxngJaEnf=REL#oM+qvO?dua6D4w!D?&9-lVc>hZQu*2~A8*4vU_N0v=xiOz=xe@ zcj=PcsatZ_wz5L$s;DHtSGRtkC995&{GEUrdX`6ZD9RGL8 zFP8kiIHrxhBAEP8CjC57uq|8i2Q?Cm){;-(ol5xI1R2|ho|2l=GrztctV`58XQjgo&uH^F~d>VHr2tsa6; z?`3H_DEayPkjt|8Sv~mWlE1YV!z_LV53&yC5$abIE9Yrb{Faj62cHI^ji1S*>>>Go z?Mqy|Pm*?1CI7|#g3r(3LHZ=WY@p!tvv-iQB>$H|g3r&~K|Ut=Pq+o2pS6ShyX4nN z6?}fi4syHXPseBPXya$=D8B%o=Bxe1#HG1KY+p)z%1Cjn#@#m(f1p_K75;$4-xh~2 zll<=_pP!jS^rYmk9WC_mvvQD)p^*H12ThpR_!&9M_L6_iP0Y6V**M7ll7Icpg3r&y zL6+dZqIvR0;HkgXGJ#hYND~zQMZuTpWBq}+C0Na@2E~G}t`i!tADUU6;`o)FUou&L zW6O1~&|~f^YUeb@Uueaj-|q&W&SFjFc~t3njPXtNb&}%K`~F7o=OgKYqr|o{U!h{< z#}fb06es?tO7IPA%W}oUHFZ9&w_Na>RjgS|v>cD7@Ebsn zyVwD@#!Gy`B*Eu*1t0^G|CCqoeM&0A3FX)g1pK=S8<-vHw%NsbrEwl0)-DC6yNS@D?U z(>G7i)=?A|y-VW!jN|W+{8>H$h<2zttDjR)5KN$NBRX@X4R??U@X|OUjzX9PlrJ-Nw@H1+wxf z=9@@C&!Rj@#9dqiXWe?>sr{#99_~@qqtpGt&dGU}$Ky@Q@&vt8u$(p+2o_A9oSbfD z`24;pSph8k@&t1{>DgF&6#z0L&od=EFU^Z}V!=Frz~e2Lfz^^ZSYwM7yvdy^MtCM; z?XSn{_j_l0uq@0!)0*tZVn$C!K~Bz0h!{s$QJNKWK!aFu#*5PYybKLZ^YA(?kG~*S zk+f7x(X>*_@?ia!om zgNOCHWL~VQlS`PHLo3~!OOgM?)@xkI%SM!OuzoADIQ)PE5^Raa;zlfJht)+usYrA?>jhkNdEzzF3DYzyZ;HS zl0t;u@3HtmfWHh?C*G2S$MegLKd8e5Sa;VsNN zynL2+vdNq(YMHjd=TAfM#0XEILFu<+#q#YQn!U84ZdSe@%V8&LMx14ysQ%RDxJ=0L zu2I7p;|7SwlNJcbE>XZu8;r*qWIc)v zUBWMaGNC#u(#gQ93Yt9N0VI#gcgiYtDs|+PA+36=^2FmwpW&tC;HA}$AQ)2Ie(?zy ztPl>&%n5qaPzL>?#0gALNFkp;-%7?5>Pw!ITacXZ&&&7ugEI|QT0vHJMhDDq0@bho zkPdV~!0=3OAk#|Dn3)R`q73>)%`~4sKp!w+m^|>H&hN|il7OW0vx8PLCwwxJOZd7(v9RH3wf+%_QesIAfB&8o?hy(1E zyeexz*p#=5C3Jx(-SSWs%C0)D3t`fw>`y8dHEt=t3b*X$<}wdJH +#include + +#if defined(__GNUC__) || defined(__clang__) +#define DATAROUTER_WEAK __attribute__((weak)) +#else +#define DATAROUTER_WEAK +#endif + +#ifndef DATAROUTER_MAX_PORTS +#define DATAROUTER_MAX_PORTS 64 +#endif + +/** + * @struct port_entry_t + * @brief 端口表条目 + * + * @var type 报文类型(DATAROUTER_CHANNEL_xxx / -1) + * @var handlers 外设发送 handler 数组 + * @var count 当前注册的 handler 数量 + * @var in_use 1=已分配,0=空闲 + */ +typedef struct { + int type; + datarouter_send_handler_t handlers[DATAROUTER_MAX_HANDLERS_PER_PORT]; + int count; + uint8_t in_use; +} port_entry_t; + +static port_entry_t ports[DATAROUTER_MAX_PORTS]; +static uint8_t initialized; + +/* ---- 通道元数据(X-macro 展开) ---- */ + +static const char *channel_names[] = { +#define DATAROUTER_CHANNEL(id, desc) #id, + DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL +}; + +static const char *channel_descs[] = { +#define DATAROUTER_CHANNEL(id, desc) desc, + DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL +}; + +/* ---- 弱符号接收回调默认实现 ---- */ + +#define DATAROUTER_CHANNEL(id, desc) \ + DATAROUTER_WEAK void datarouter_on_recv_##id( \ + uint32_t port_id, const void *data, size_t len) \ + { \ + (void)port_id; (void)data; (void)len; \ + } +DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL + +/* ---- 发送触发函数实现 ---- */ + +#define DATAROUTER_CHANNEL(id, desc) \ + int datarouter_send_##id(uint32_t port_id, const void *data, size_t len) \ + { \ + if (!initialized) return -1; \ + if (port_id >= DATAROUTER_MAX_PORTS || !ports[port_id].in_use) return -2; \ + if (ports[port_id].type != DATAROUTER_CHANNEL_##id) return -3; \ + if (ports[port_id].count == 0) return -4; \ + for (int _i = 0; _i < ports[port_id].count; _i++) \ + ports[port_id].handlers[_i](port_id, data, len); \ + return 0; \ + } +DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL + +/* ---- 端口管理 API ---- */ + +void datarouter_init(void) +{ + if (initialized) return; + memset(ports, 0, sizeof(ports)); + initialized = 1; +} + +void datarouter_reset(void) +{ + initialized = 0; + memset(ports, 0, sizeof(ports)); +} + +datarouter_port_t datarouter_port_create(void) +{ + if (!initialized) return DATAROUTER_INVALID_PORT; + for (int i = 0; i < DATAROUTER_MAX_PORTS; i++) { + if (!ports[i].in_use) { + ports[i].in_use = 1; + ports[i].type = -1; + ports[i].count = 0; + return (datarouter_port_t)i; + } + } + return DATAROUTER_INVALID_PORT; +} + +void datarouter_port_delete(datarouter_port_t port) +{ + if (!initialized) return; + if (port < 0 || port >= DATAROUTER_MAX_PORTS) return; + ports[port].in_use = 0; +} + +void datarouter_port_set_type(datarouter_port_t port, int channel_id) +{ + if (!initialized) return; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return; + ports[port].type = channel_id; +} + +int datarouter_port_get_type(datarouter_port_t port) +{ + if (!initialized) return -1; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return -1; + return ports[port].type; +} + +int datarouter_port_add_handler(datarouter_port_t port, datarouter_send_handler_t handler) +{ + if (!initialized) return -1; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return -1; + if (ports[port].count >= DATAROUTER_MAX_HANDLERS_PER_PORT) return -2; + for (int i = 0; i < ports[port].count; i++) + if (ports[port].handlers[i] == handler) return -3; + ports[port].handlers[ports[port].count++] = handler; + return 0; +} + +int datarouter_port_remove_handler(datarouter_port_t port, datarouter_send_handler_t handler) +{ + if (!initialized) return -1; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return -1; + for (int i = 0; i < ports[port].count; i++) { + if (ports[port].handlers[i] == handler) { + ports[port].handlers[i] = ports[port].handlers[--ports[port].count]; + return 0; + } + } + return -2; +} + +void datarouter_port_clear_handlers(datarouter_port_t port) +{ + if (!initialized) return; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return; + ports[port].count = 0; +} + +int datarouter_port_handler_count(datarouter_port_t port) +{ + if (!initialized) return -1; + if (port < 0 || port >= DATAROUTER_MAX_PORTS || !ports[port].in_use) return -1; + return ports[port].count; +} + +/* ---- 通道元数据查询 ---- */ + +int datarouter_channel_count(void) +{ + return DATAROUTER_CHANNEL_COUNT; +} + +const char *datarouter_channel_name(int channel_id) +{ + if (channel_id < 0 || channel_id >= DATAROUTER_CHANNEL_COUNT) return NULL; + return channel_names[channel_id]; +} + +const char *datarouter_channel_desc(int channel_id) +{ + if (channel_id < 0 || channel_id >= DATAROUTER_CHANNEL_COUNT) return NULL; + return channel_descs[channel_id]; +} diff --git a/datarouter.h b/datarouter.h new file mode 100644 index 0000000..0e449ef --- /dev/null +++ b/datarouter.h @@ -0,0 +1,162 @@ +/** + * @file datarouter.h + * @brief 数据路由库 —— 公共接口 + * + * ## 概念 + * + * - **通道 (Channel)**:编译期声明的报文类型(如 sensor / status / control) + * - **端口 (Port)**:运行期创建的连接点,绑定一个报文类型和多个外设发送 handler + * - **路由**:将报文类型关联到一个或多个物理外设发送函数 + * - **多外设**:一个端口可注册多个 handler,发送时逐一调用全部 handler + * + * ## 使用流程 + * + * 1. 在 datarouter_config.h 中声明报文类型 + * 2. 调用 datarouter_init() 初始化 + * 3. 通过 datarouter_port_create() 创建端口 + * 4. 调用 datarouter_port_set_type() 指定端口处理的报文类型 + * 5. 调用 datarouter_port_add_handler() 注册一个或多个外设发送函数 + * 6. 调用 datarouter_send_() 触发发送(库逐一调用所有注册的 handler) + * 7. 实现 datarouter_on_recv_() 处理从外设收到的报文 + * 8. datarouter_port_delete() 移除端口 + * + * ## 路由规则 + * + * datarouter_send_(port, data, len) + * → 校验端口类型 + * → for each handler: handler(port, data, len) + * → return 0 + * + * 接收:外设收到数据 → 用户识别报文类型 → 调 datarouter_on_recv_(port, data, len) + */ + +#ifndef DATAROUTER_H +#define DATAROUTER_H + +#include +#include + +#include "datarouter_config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @enum datarouter_channel + * @brief 报文类型枚举 + * + * 由 DATAROUTER_CHANNELS 宏列表自动生成。 + */ +enum datarouter_channel { +#define DATAROUTER_CHANNEL(id, desc) DATAROUTER_CHANNEL_##id, + DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL + DATAROUTER_CHANNEL_COUNT +}; + +/** + * @brief 外设发送 handler 类型 + * + * @param[in] port_id 源端口 ID + * @param[in] data 数据缓冲区 + * @param[in] len 数据长度 + */ +typedef void (*datarouter_send_handler_t)(uint32_t port_id, const void *data, size_t len); + +#ifndef DATAROUTER_MAX_HANDLERS_PER_PORT +#define DATAROUTER_MAX_HANDLERS_PER_PORT 4 +#endif + +/** + * @defgroup send_functions 发送触发函数 + * + * 每个报文类型生成一个发送触发函数。 + * 内部校验端口后,逐一调用端口上注册的所有 handler。 + * @{ + */ + +#define DATAROUTER_CHANNEL(id, desc) \ + int datarouter_send_##id(uint32_t port_id, const void *data, size_t len); +DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL + +/** @} */ + +/** + * @defgroup recv_callbacks 接收回调声明 + * + * 每个报文类型生成一个接收回调声明。用户实现。 + * @{ + */ + +#define DATAROUTER_CHANNEL(id, desc) \ + void datarouter_on_recv_##id(uint32_t port_id, const void *data, size_t len); +DATAROUTER_CHANNELS +#undef DATAROUTER_CHANNEL + +/** @} */ + +/** + * @defgroup port_api 端口管理 API + * + * 每个端口可注册 N 个外设发送 handler(上限 DATAROUTER_MAX_HANDLERS_PER_PORT)。 + * 发送时逐一调用全部 handler,实现一对多广播路由。 + * @{ + */ + +typedef int32_t datarouter_port_t; +#define DATAROUTER_INVALID_PORT ((datarouter_port_t)-1) + +void datarouter_init(void); +void datarouter_reset(void); + +datarouter_port_t datarouter_port_create(void); +void datarouter_port_delete(datarouter_port_t port); + +void datarouter_port_set_type(datarouter_port_t port, int channel_id); +int datarouter_port_get_type(datarouter_port_t port); + +/** + * @brief 为端口添加一个外设发送 handler + * @param[in] port 端口 ID + * @param[in] handler 发送函数指针(不可为 NULL) + * @retval 0 成功 + * @retval -1 端口无效 + * @retval -2 handler 列表已满 + * @retval -3 handler 已存在(不重复添加) + */ +int datarouter_port_add_handler(datarouter_port_t port, datarouter_send_handler_t handler); + +/** + * @brief 从端口移除一个外设发送 handler + * @param[in] port 端口 ID + * @param[in] handler 要移除的函数指针 + * @retval 0 成功 + * @retval -1 端口无效 + * @retval -2 handler 未找到 + */ +int datarouter_port_remove_handler(datarouter_port_t port, datarouter_send_handler_t handler); + +/** @brief 清除端口所有 handler */ +void datarouter_port_clear_handlers(datarouter_port_t port); + +/** @brief 获取端口当前 handler 数量 @return handler 数量,-1 表示端口无效 */ +int datarouter_port_handler_count(datarouter_port_t port); + +/** @} */ + +/** + * @defgroup introspection 报文类型元数据查询 + * @{ + */ +int datarouter_channel_count(void); +const char *datarouter_channel_name(int channel_id); +const char *datarouter_channel_desc(int channel_id); +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* DATAROUTER_H */ \ No newline at end of file diff --git a/datarouter_config.h b/datarouter_config.h new file mode 100644 index 0000000..26b0c3c --- /dev/null +++ b/datarouter_config.h @@ -0,0 +1,34 @@ +/** + * @file datarouter_config.h + * @brief 数据报文类型配置 + * + * 用户在此文件中定义所有报文类型(通道)。 + * 每个报文类型通过 DATAROUTER_CHANNEL(id, desc) 宏声明, + * 库在编译期据此自动生成: + * - 通道枚举值 DATAROUTER_CHANNEL_ + * - 发送触发函数 datarouter_send_(port, data, len) + * - 接收回调声明 datarouter_on_recv_(port, data, len) + */ + +#ifndef DATAROUTER_CONFIG_H +#define DATAROUTER_CONFIG_H + +/** + * @def DATAROUTER_CHANNELS + * @brief 报文类型列表宏 + * + * 每行格式:DATAROUTER_CHANNEL(标识符, "描述文字") + * 用户按需增删改。每个标识符对应一种报文类型。 + * + * @code + * #define DATAROUTER_CHANNELS \ + * DATAROUTER_CHANNEL(sensor, "Sensor data") \ + * DATAROUTER_CHANNEL(control, "Control command") + * @endcode + */ +#define DATAROUTER_CHANNELS \ + DATAROUTER_CHANNEL(sensor, "Sensor data") \ + DATAROUTER_CHANNEL(status, "Status report") \ + DATAROUTER_CHANNEL(control, "Control command") + +#endif /* DATAROUTER_CONFIG_H */ \ No newline at end of file diff --git a/example.c b/example.c new file mode 100644 index 0000000..d5b4f46 --- /dev/null +++ b/example.c @@ -0,0 +1,105 @@ +/** + * @file example.c + * @brief 数据路由库使用示例 + * + * 展示: + * 1. 编译期声明报文类型 → 自动生成发送触发函数和接收回调声明 + * 2. 创建端口、设置报文类型 + * 3. 为一个端口注册多个外设 handler(一对多广播路由) + * 4. 运行时增删 handler,切换路由 + * 5. 实现接收回调处理外设收到的数据 + */ + +#include "datarouter.h" +#include +#include + +/* ---- 外设发送 handler 实现 ---- */ + +static void uart_send(uint32_t port_id, const void *data, size_t len) +{ + printf("[UART] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +static void tcp_send(uint32_t port_id, const void *data, size_t len) +{ + printf("[TCP] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +static void file_send(uint32_t port_id, const void *data, size_t len) +{ + printf("[FILE] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +static void can_send(uint32_t port_id, const void *data, size_t len) +{ + printf("[CAN] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +/* ---- 接收回调实现 ---- */ + +void datarouter_on_recv_sensor(uint32_t port_id, const void *data, size_t len) +{ + printf("[RX:sensor] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +void datarouter_on_recv_status(uint32_t port_id, const void *data, size_t len) +{ + printf("[RX:status] port %u: %.*s\n", port_id, (int)len, (const char *)data); +} + +/* ---- main ---- */ + +int main(void) +{ + datarouter_init(); + + printf("Channels:\n"); + for (int i = 0; i < datarouter_channel_count(); i++) + printf(" [%d] %s - %s\n", i, + datarouter_channel_name(i), + datarouter_channel_desc(i)); + + /* + * 创建一个 sensor 端口,注册多个 handler。 + * 一次 send_sensor() 调用同时路由到 UART + TCP + CAN。 + */ + datarouter_port_t sensor_port = datarouter_port_create(); + datarouter_port_set_type(sensor_port, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(sensor_port, uart_send); + datarouter_port_add_handler(sensor_port, tcp_send); + datarouter_port_add_handler(sensor_port, can_send); + + printf("\n--- send sensor (UART + TCP + CAN) ---\n"); + datarouter_send_sensor(sensor_port, "temperature:25.3", 16); + + /* + * 运行时移除 TCP handler,只剩 UART + CAN。 + */ + printf("\n--- remove TCP, keep UART + CAN ---\n"); + datarouter_port_remove_handler(sensor_port, tcp_send); + datarouter_send_sensor(sensor_port, "temperature:26.1", 16); + + /* + * 运行时再添加 FILE handler。 + */ + printf("\n--- add FILE back, now UART + CAN + FILE ---\n"); + datarouter_port_add_handler(sensor_port, file_send); + datarouter_send_sensor(sensor_port, "temperature:27.0", 16); + + /* + * 清除所有 handler,不再输出到任何外设。 + */ + printf("\n--- clear all handlers, nothing sent ---\n"); + datarouter_port_clear_handlers(sensor_port); + int ret = datarouter_send_sensor(sensor_port, "lost", 4); + printf("send_sensor after clear returned %d\n", ret); + + /* ---- 接收 ---- */ + printf("\n--- simulate peripheral receive ---\n"); + datarouter_on_recv_sensor(0, "temperature:24.8", 16); + datarouter_on_recv_status(1, "status:ERROR", 12); + + datarouter_port_delete(sensor_port); + return 0; +} diff --git a/library.c b/library.c deleted file mode 100644 index f431745..0000000 --- a/library.c +++ /dev/null @@ -1,7 +0,0 @@ -#include "library.h" - -#include - -void hello(void) { - printf("Hello, World!\n"); -} diff --git a/library.h b/library.h deleted file mode 100644 index f16761c..0000000 --- a/library.h +++ /dev/null @@ -1,6 +0,0 @@ -#ifndef DATAROUTER_LIBRARY_H -#define DATAROUTER_LIBRARY_H - -void hello(void); - -#endif // DATAROUTER_LIBRARY_H diff --git a/tests/test_datarouter.c b/tests/test_datarouter.c new file mode 100644 index 0000000..43eef52 --- /dev/null +++ b/tests/test_datarouter.c @@ -0,0 +1,511 @@ +/** + * @file test_datarouter.c + * @brief 数据路由库单元测试 + * + * 覆盖: + * - 端口管理(创建/删除/复用/边界) + * - 端口类型与多 handler 增删查 + * - 通道元数据查询 + * - 一对多广播发送(多 handler 逐一调用) + * - 运行时增删 handler 改变路由 + * - 接收回调 + * - 错误处理 + */ + +#include "datarouter.h" +#include +#include + +/* ================================================================ + * 测试框架 + * ================================================================ */ + +static int _total_run, _total_pass, _total_fail; +static int _err; +static const char *_cur_name; + +#define FIXTURE_MAX 16 +typedef struct { + datarouter_port_t ports[FIXTURE_MAX]; + int count; +} fixture_t; + +#define FIXTURE_INIT(f) memset((f), 0, sizeof(*(f))) +#define FIXTURE_PUSH(f, p) do { if ((f)->count < FIXTURE_MAX) (f)->ports[(f)->count++] = (p); } while (0) +#define FIXTURE_CLEAN(f) do { for (int _i = 0; _i < (f)->count; _i++) datarouter_port_delete((f)->ports[_i]); } while (0) +#define FIXTURE_CREATE(f) ({ datarouter_port_t _p = datarouter_port_create(); FIXTURE_PUSH(f, _p); _p; }) + +#define TEST_START(name) \ + static void test_##name(void) \ + { \ + fixture_t _fix; \ + FIXTURE_INIT(&_fix); \ + _cur_name = #name; \ + _err = 0; + +#define TEST_END() \ +cleanup: \ + FIXTURE_CLEAN(&_fix); \ + if (_err) { _total_fail++; printf(" FAIL %s\n", _cur_name); } \ + else { _total_pass++; printf(" PASS %s\n", _cur_name); } \ + _total_run++; \ + } + +#define CHECK(cond, ...) do { \ + if (!(cond)) { \ + printf(" %s [line %d]: ", _cur_name, __LINE__); \ + printf(__VA_ARGS__); \ + printf("\n"); \ + _err = 1; \ + goto cleanup; \ + } \ + } while (0) + +/* ================================================================ + * 测试用 handler + * ================================================================ */ + +static int _h_cnt; +static int _h_total; +static int _h_ids[16]; +static void _h(uint32_t p, const void *d, size_t l) +{ + (void)d; (void)l; + if (_h_cnt < 16) _h_ids[_h_cnt] = (int)p; + _h_cnt++; + _h_total++; +} + +static void _h_a(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _h_cnt++; } +static void _h_b(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _h_cnt++; } +static void _h_c(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _h_cnt++; } +static void _h_d(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _h_cnt++; } + +static void reset_hstate(void) +{ + _h_cnt = _h_total = 0; + memset(_h_ids, 0, sizeof(_h_ids)); +} + +/* ================================================================ + * 1. 未初始化 + * ================================================================ */ + +TEST_START(error_uninit) + int ret = datarouter_send_sensor(0, "d", 1); + CHECK(ret == -1, "expected -1, got %d", ret); +TEST_END() + +/* ================================================================ + * 2. 端口管理 + * ================================================================ */ + +TEST_START(port_create_basic) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(p >= 0, "create returned %d", p); +TEST_END() + +TEST_START(port_create_multiple) + datarouter_port_t p1 = FIXTURE_CREATE(&_fix); + datarouter_port_t p2 = FIXTURE_CREATE(&_fix); + datarouter_port_t p3 = FIXTURE_CREATE(&_fix); + CHECK(p1 >= 0 && p2 >= 0 && p3 >= 0, "some invalid"); + CHECK(p1 != p2 && p2 != p3, "IDs should be unique"); +TEST_END() + +TEST_START(port_create_reuse) + datarouter_port_t p1 = FIXTURE_CREATE(&_fix); + datarouter_port_delete(p1); + datarouter_port_t p2 = FIXTURE_CREATE(&_fix); + CHECK(p1 == p2, "expected reuse ID %d, got %d", p1, p2); +TEST_END() + +TEST_START(port_delete_invalid) + datarouter_port_delete(-1); + datarouter_port_delete(9999); +TEST_END() + +TEST_START(port_delete_twice) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_delete(p); + datarouter_port_delete(p); + _fix.count--; +TEST_END() + +TEST_START(port_default_state) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_get_type(p) == -1, "type should be -1"); + CHECK(datarouter_port_handler_count(p) == 0, "handler count should be 0"); +TEST_END() + +TEST_START(port_create_max) + int created = 0; + for (int i = 0; i < 64; i++) { + datarouter_port_t p = datarouter_port_create(); + if (p == DATAROUTER_INVALID_PORT) break; + FIXTURE_PUSH(&_fix, p); + created++; + } + CHECK(created >= 64, "expected 64, got %d", created); + CHECK(datarouter_port_create() == DATAROUTER_INVALID_PORT, + "expected INVALID after exhaustion"); +TEST_END() + +/* ================================================================ + * 3. 类型 + * ================================================================ */ + +TEST_START(type_set_get) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + CHECK(datarouter_port_get_type(p) == DATAROUTER_CHANNEL_sensor, "type mismatch"); +TEST_END() + +TEST_START(type_clear) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_set_type(p, -1); + CHECK(datarouter_port_get_type(p) == -1, "should be -1"); +TEST_END() + +TEST_START(type_switch) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_status); + CHECK(datarouter_port_get_type(p) == DATAROUTER_CHANNEL_status, "should be status"); +TEST_END() + +TEST_START(type_from_invalid) + CHECK(datarouter_port_get_type(-1) == -1, NULL); + CHECK(datarouter_port_get_type(999) == -1, NULL); +TEST_END() + +/* ================================================================ + * 4. 多 handler 增删查 + * ================================================================ */ + +TEST_START(handler_add_one) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_add_handler(p, _h) == 0, "add failed"); + CHECK(datarouter_port_handler_count(p) == 1, "count=%d", datarouter_port_handler_count(p)); +TEST_END() + +TEST_START(handler_add_multiple) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_add_handler(p, _h_a) == 0, "add a"); + CHECK(datarouter_port_add_handler(p, _h_b) == 0, "add b"); + CHECK(datarouter_port_add_handler(p, _h_c) == 0, "add c"); + CHECK(datarouter_port_handler_count(p) == 3, "count=%d", datarouter_port_handler_count(p)); +TEST_END() + +TEST_START(handler_add_duplicate) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_add_handler(p, _h) == 0, "first add"); + CHECK(datarouter_port_add_handler(p, _h) == -3, "duplicate should return -3"); +TEST_END() + +TEST_START(handler_add_max) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_add_handler(p, _h_a) == 0, "add a"); + CHECK(datarouter_port_add_handler(p, _h_b) == 0, "add b"); + CHECK(datarouter_port_add_handler(p, _h_c) == 0, "add c"); + CHECK(datarouter_port_add_handler(p, _h_d) == 0, "add d"); + CHECK(datarouter_port_handler_count(p) == DATAROUTER_MAX_HANDLERS_PER_PORT, + "count=%d", datarouter_port_handler_count(p)); + /* 第 5 个应返回 -2(已满) */ + CHECK(datarouter_port_add_handler(p, _h) == -2, + "expected -2 (full), got %d", datarouter_port_add_handler(p, _h)); +TEST_END() + +TEST_START(handler_remove) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_add_handler(p, _h_a); + datarouter_port_add_handler(p, _h_b); + datarouter_port_add_handler(p, _h_c); + CHECK(datarouter_port_remove_handler(p, _h_b) == 0, "remove b"); + CHECK(datarouter_port_handler_count(p) == 2, "count should be 2, got %d", datarouter_port_handler_count(p)); +TEST_END() + +TEST_START(handler_remove_not_found) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + CHECK(datarouter_port_remove_handler(p, _h) == -2, "expected -2"); +TEST_END() + +TEST_START(handler_clear) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_add_handler(p, _h_a); + datarouter_port_add_handler(p, _h_b); + datarouter_port_clear_handlers(p); + CHECK(datarouter_port_handler_count(p) == 0, "count should be 0"); +TEST_END() + +TEST_START(handler_from_invalid) + CHECK(datarouter_port_handler_count(-1) == -1, NULL); + CHECK(datarouter_port_handler_count(999) == -1, NULL); + CHECK(datarouter_port_add_handler(-1, _h) == -1, NULL); + CHECK(datarouter_port_remove_handler(999, _h) == -1, NULL); +TEST_END() + +/* ================================================================ + * 5. 通道元数据 + * ================================================================ */ + +TEST_START(channel_count) + CHECK(datarouter_channel_count() == 3, "expected 3"); +TEST_END() + +TEST_START(channel_names) + CHECK(strcmp(datarouter_channel_name(0), "sensor") == 0, NULL); + CHECK(strcmp(datarouter_channel_name(1), "status") == 0, NULL); + CHECK(strcmp(datarouter_channel_name(2), "control") == 0, NULL); +TEST_END() + +TEST_START(channel_descs) + CHECK(strcmp(datarouter_channel_desc(0), "Sensor data") == 0, NULL); + CHECK(strcmp(datarouter_channel_desc(1), "Status report") == 0, NULL); + CHECK(strcmp(datarouter_channel_desc(2), "Control command") == 0, NULL); +TEST_END() + +TEST_START(channel_invalid_id) + CHECK(datarouter_channel_name(-1) == NULL, NULL); + CHECK(datarouter_channel_name(99) == NULL, NULL); + CHECK(datarouter_channel_desc(-1) == NULL, NULL); + CHECK(datarouter_channel_desc(99) == NULL, NULL); +TEST_END() + +/* ================================================================ + * 6. 发送 — 一对多广播 + * ================================================================ */ + +TEST_START(send_one_handler) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h); + + reset_hstate(); + CHECK(datarouter_send_sensor(p, "d", 1) == 0, "send failed"); + CHECK(_h_cnt == 1, "handler called %d times", _h_cnt); +TEST_END() + +TEST_START(send_multi_handlers) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h_a); + datarouter_port_add_handler(p, _h_b); + datarouter_port_add_handler(p, _h_c); + + reset_hstate(); + CHECK(datarouter_send_sensor(p, "d", 1) == 0, "send failed"); + CHECK(_h_cnt == 3, "expected 3 handler calls, got %d", _h_cnt); +TEST_END() + +TEST_START(send_type_mismatch) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h); + + reset_hstate(); + CHECK(datarouter_send_status(p, "d", 1) == -3, "expected -3"); + CHECK(_h_cnt == 0, "handler should not fire"); +TEST_END() + +TEST_START(send_no_handler) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + + reset_hstate(); + CHECK(datarouter_send_sensor(p, "d", 1) == -4, "expected -4"); +TEST_END() + +TEST_START(send_three_channels) + /* 三个通道各一个端口 */ + datarouter_port_t p1 = FIXTURE_CREATE(&_fix); + datarouter_port_t p2 = FIXTURE_CREATE(&_fix); + datarouter_port_t p3 = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p1, DATAROUTER_CHANNEL_sensor); + datarouter_port_set_type(p2, DATAROUTER_CHANNEL_status); + datarouter_port_set_type(p3, DATAROUTER_CHANNEL_control); + datarouter_port_add_handler(p1, _h); + datarouter_port_add_handler(p2, _h); + datarouter_port_add_handler(p3, _h); + + reset_hstate(); + CHECK(datarouter_send_sensor(p1, "a", 1) == 0, NULL); + CHECK(datarouter_send_status(p2, "b", 1) == 0, NULL); + CHECK(datarouter_send_control(p3, "c", 1) == 0, NULL); + CHECK(_h_cnt == 3, "expected 3 total calls, got %d", _h_cnt); +TEST_END() + +/* ================================================================ + * 7. 发送 — 运行时增删 handler + * ================================================================ */ + +TEST_START(send_remove_handler) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h_a); + datarouter_port_add_handler(p, _h_b); + + reset_hstate(); + datarouter_send_sensor(p, "d", 1); + CHECK(_h_cnt == 2, "2 handlers: count=%d", _h_cnt); + + datarouter_port_remove_handler(p, _h_a); + + reset_hstate(); + datarouter_send_sensor(p, "d", 1); + CHECK(_h_cnt == 1, "after remove: count=%d", _h_cnt); +TEST_END() + +TEST_START(send_clear_handlers) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h_a); + datarouter_port_add_handler(p, _h_b); + datarouter_port_clear_handlers(p); + + CHECK(datarouter_send_sensor(p, "d", 1) == -4, "expected -4 after clear"); +TEST_END() + +/* ================================================================ + * 8. 接收回调 + * ================================================================ */ + +static int _rx_s; +static void rx_s(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _rx_s++; } +static int _rx_t; +static void rx_t(uint32_t p, const void *d, size_t l) { (void)p; (void)d; (void)l; _rx_t++; } + +void datarouter_on_recv_sensor(uint32_t p, const void *d, size_t l) { rx_s(p, d, l); } +void datarouter_on_recv_status(uint32_t p, const void *d, size_t l) { rx_t(p, d, l); } + +TEST_START(recv_callback) + _rx_s = _rx_t = 0; + datarouter_on_recv_sensor(0, "data", 4); + CHECK(_rx_s == 1, "sensor cb: %d", _rx_s); + CHECK(_rx_t == 0, "status cb: %d", _rx_t); + datarouter_on_recv_status(1, "data", 4); + CHECK(_rx_t == 1, "status cb: %d", _rx_t); + CHECK(_rx_s == 1, "sensor cb unchanged: %d", _rx_s); +TEST_END() + +/* ================================================================ + * 9. 错误处理 + * ================================================================ */ + +TEST_START(error_invalid_port) + CHECK(datarouter_send_sensor(999, "d", 1) == -2, NULL); + CHECK(datarouter_send_sensor(-1, "d", 1) == -2, NULL); +TEST_END() + +TEST_START(error_deleted_port) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h); + datarouter_port_delete(p); + CHECK(datarouter_send_sensor(p, "d", 1) == -2, "expected -2"); + _fix.count--; +TEST_END() + +/* ================================================================ + * 10. 零长与 NULL + * ================================================================ */ + +TEST_START(send_zero_length) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h); + + reset_hstate(); + CHECK(datarouter_send_sensor(p, "x", 0) == 0, NULL); + CHECK(_h_cnt == 1, "handler called %d", _h_cnt); +TEST_END() + +TEST_START(send_null_data) + datarouter_port_t p = FIXTURE_CREATE(&_fix); + datarouter_port_set_type(p, DATAROUTER_CHANNEL_sensor); + datarouter_port_add_handler(p, _h); + + reset_hstate(); + CHECK(datarouter_send_sensor(p, NULL, 0) == 0, NULL); + CHECK(_h_cnt == 1, "handler called %d", _h_cnt); +TEST_END() + +/* ================================================================ + * 主函数 + * ================================================================ */ + +#define RUN(name) do { reset_hstate(); test_##name(); } while (0) + +int main(void) +{ + printf("==== DataRouter Unit Tests ====\n\n"); + + printf("[uninitialized]\n"); + RUN(error_uninit); + + datarouter_init(); + + printf("\n[port management]\n"); + RUN(port_create_basic); + RUN(port_create_multiple); + RUN(port_create_reuse); + RUN(port_delete_invalid); + RUN(port_delete_twice); + RUN(port_default_state); + RUN(port_create_max); + printf("\n"); + + datarouter_reset(); datarouter_init(); + printf("[type set/get]\n"); + RUN(type_set_get); + RUN(type_clear); + RUN(type_switch); + RUN(type_from_invalid); + printf("\n"); + + printf("[multi-handler add/remove]\n"); + RUN(handler_add_one); + RUN(handler_add_multiple); + RUN(handler_add_duplicate); + RUN(handler_add_max); + RUN(handler_remove); + RUN(handler_remove_not_found); + RUN(handler_clear); + RUN(handler_from_invalid); + printf("\n"); + + printf("[channel introspection]\n"); + RUN(channel_count); + RUN(channel_names); + RUN(channel_descs); + RUN(channel_invalid_id); + printf("\n"); + + printf("[send — one-to-many broadcast]\n"); + RUN(send_one_handler); + RUN(send_multi_handlers); + RUN(send_type_mismatch); + RUN(send_no_handler); + RUN(send_three_channels); + RUN(send_remove_handler); + RUN(send_clear_handlers); + RUN(send_zero_length); + RUN(send_null_data); + printf("\n"); + + printf("[receive callback]\n"); + RUN(recv_callback); + printf("\n"); + + printf("[error handling]\n"); + RUN(error_invalid_port); + RUN(error_deleted_port); + printf("\n"); + + printf("==============================\n"); + printf(" total: %d | pass: %d | fail: %d\n\n", + _total_run, _total_pass, _total_fail); + + return _total_fail > 0 ? 1 : 0; +} From c3af3ae4e287176d5da28e555aa8219213a64bc4 Mon Sep 17 00:00:00 2001 From: 7iwind <7wind@7iwind.cn> Date: Sun, 26 Jul 2026 01:46:47 +0800 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=E5=AE=8C=E5=96=84README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/misc.xml | 1 + README.md | 166 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+) diff --git a/.idea/misc.xml b/.idea/misc.xml index 0b76fe5..e479d63 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -4,4 +4,5 @@