原生库测试流程

项目 内容
适用 sign 仓,原生库(C 服务接口)测试,不依赖持续集成
sign 提交 main @ 13843ad3
接口 tool/native/gwpack.h:配置、打包、取文件名、取错、版本号,共 5 个函数
测试方式 C 动态加载服务检查 + Java 示例(用 sign-server 已起好的服务,最后测)

原生库最后测:验证 C 服务接口本身(不是命令行、不是本地打包)。原生库只对接 sign-server 的服务接口:它不持有签名种子,不做本地打包、验证、推导。本地打包与验证走 Rust 库与命令行(见命令行文档)。前置:单测与服务检查已通过,服务正跑着(不要另起服务,直接用同一个服务和凭证)。编一次,三个平台产物都在仓根 target/release/:libsign_tool.dylib(Mac)、libsign_tool.so(Linux)、sign_tool.dll(Windows)。

相关仓库:sign https://github.com/ffffxxxjjj/sign ;gateway https://github.com/ffffxxxjjj/gateway ;firmware-tool https://github.com/ffffxxxjjj/firmware-tool 。

五、native

接口定义(tool/native/gwpack.h,共 5 个函数)

函数名是裸名,C 符号与 Java 方法同名。

通用约定:

状态码 值 含义
OK 0 成功
ERR_NULL -1 必填指针为空
ERR_SMALL_BUFFER -2 缓冲区太小;所需长度写进长度参数,不写字节
ERR_FAILED -3 失败;原因用 last_error 取
ERR_PANIC -4 内部 panic 已拦下;原因用 last_error 取

configure —— 配置服务客户端

int configure(
    const char *base_url,
    const char *api_key,
    const char *api_secret);

参数:

参数 说明
base_url 服务地址,可空:空指针或空串用默认 http://127.0.0.1:3000;尾斜杠剥除;非 UTF-8 拒绝
api_key 管理页分配的调用凭证 key,必填:空指针或空串都拒绝;非 UTF-8 拒绝
api_secret 管理页分配的调用凭证 secret,必填:同上

返回:

返回 含义
OK 配置成功;重复配置覆盖上一次
ERR_NULL api_key 或 api_secret 为空指针
ERR_FAILED key 或 secret 为空串,或任一字符串非 UTF-8;原因用 last_error 取

备注:这是机器调用凭证,不是管理员登录;必须在 pack 之前调。

pack —— 经服务打包

int pack(
    const unsigned char *firmware, size_t firmware_len,
    const char *product,
    const char *remark,
    unsigned char *out, size_t *out_len);

参数:

参数 说明
firmware / firmware_len 固件字节与长度;空指针仅当长度为 0 时合法(空固件会被服务端拒绝);版本、型号、固件名由服务端从固件版本块(格式 2,偏移 0x1000)读,无版本块服务端拒绝
product 产品名,可空:空指针或空串服务端默认 gateway
remark 备注,可空:空指针视为空串;非 UTF-8 拒绝
out / out_len 输出缓冲区与长度;out_len 为空指针拒绝

返回:

返回 含义
OK 成功;out_len 为包长(头 312 + 固件 + 标签 16);服务给的文件名用 last_filename 取
OK(out 为空指针时) 问长度:out_len 为所需长度,不发请求、不写字节(长度直接由包格式算出:312 +固件长+16,所以一次逻辑打包只发一次请求)
ERR_NULL out_len 为空指针,或 firmware(长度非 0 时)为空指针
ERR_SMALL_BUFFER 缓冲区太小;out_len 为所需长度、不写字节;正确用法是先空指针问长度,按长度分配(问长度不发请求,不会多存记录)
ERR_FAILED 未配置、服务端拒绝(无版本块、空固件、凭证错、时间戳超窗、nonce 重放)、网络失败;原因用 last_error 取
ERR_PANIC 内部 panic 已拦下;原因用 last_error 取

备注:共用包不收设备密钥,pack 没有设备密钥参数;一台设备的包密钥包经服务端 GET /key-package 取,不走原生库。

last_filename —— 取上次服务给的文件名

int last_filename(char *buf, size_t buf_len);

参数:

参数 说明
buf / buf_len 调用方缓冲区与长度;buf 为空指针或 buf_len 为 0 时是问长度,不写字节

返回:文件名的完整长度(不含 NUL),所有情况下都返回完整长度;缓冲区太小则写截断后的 NUL 结尾副本,调用方拿返回值与 buf_len 比较可发现截断;本线程没成功打包过返回 0(空串)。

last_error —— 取调用线程上次失败的原因

int last_error(char *buf, size_t buf_len);

参数:同上(缓冲区与长度;空指针或长度 0 问长度)。

返回:同上协议;长度 0 表示本线程上次调用没失败,或还没调过。

lib_version —— 取库版本号

const char *lib_version(void);

参数:无。

返回:库版本号字符串指针(当前 0.1.0);指针永久有效,不用释放、不要 free;保证非空。

Rust 单测(已跑过)

服务接口的三项测试(pack_without_configure_fails、configure_rejects_bad_inputs、lib_version_is_non_empty:未配置拒收、非法配置拒收、版本号非空)已随库单测跑过(不用联网、不用起服务),这里不再重跑。

C 动态加载检查

tool/native/ffi-test/c_dlopen_test.c:不链接库,运行时 dlopen 加载,依次验:版本号非空、未配置打包拒收、空 key 配置拒收、配好后对真服务打包(8192 字节测试固件,包 8520 字节、包头 GWPK + 格式 3)、服务给的文件名非空且以 .pkg 结尾。证明导出的 C 服务接口经真实动态加载可用。完整验证(签名与解密往返)不在这里,由 Rust 服务示例与命令行验包步骤覆盖。

在持续集成里最后跑(同款);本地跑时用 sign-server 已起好的服务和凭证,不要另起服务:

逐行跑(行内代码点一下就复制):

  1. cargo build --release --workspace
  2. gcc -o /tmp/c_dlopen_test tool/native/ffi-test/c_dlopen_test.c -ldl
  3. /tmp/c_dlopen_test ./target/release/libsign_tool.so http://127.0.0.1:3009 "<key>" "<secret>"

预期:打印库版本、打包 8520 字节、服务给的文件名,末行 ALL C DLOPEN CHECKS PASSED,退出 0。

Java 服务映射

tool/native/java/Gwpack.java 是 JNA 映射,方法与 C 函数一一对应(size_t 用 LongByReference 映射,64 位平台):

int configure(String baseUrl, String apiKey, String apiSecret);
int pack(byte[] firmware, long firmwareLen,
        String product, String remark, byte[] out, LongByReference outLen);
int last_filename(byte[] buf, long bufLen);
int last_error(byte[] buf, long bufLen);
String lib_version();

另带三个辅助(失败抛 IllegalStateException,错误信息里含 lastError;没成功过、没失败过返回空串):

static byte[] servicePack(Gwpack lib, byte[] firmware, String product, String remark);
static String serviceLastFilename(Gwpack lib);
static String lastError(Gwpack lib);

加载:Gwpack lib = Native.load("sign_tool", Gwpack.class);(JNA 自动加平台前后缀)。

Java 示例:经原生库打包

tool/native/java/GwpackExample.java(完整程序,签名种子不出服务;本程序只持有分配的 key + secret,后者是公开的打包参数之外的凭证):

import com.sun.jna.Native;
import java.nio.file.Files;
import java.nio.file.Path;

public class GwpackExample {

    public static void main(String[] args) throws Exception {
        if (args.length != 4) {
            System.err.println("usage: GwpackExample <base-url> <api-key> <api-secret> <firmware-file>");
            System.exit(2);
        }
        Gwpack lib = Native.load("sign_tool", Gwpack.class);
        System.out.println("lib version: " + lib.lib_version());

        int rc = lib.configure(args[0], args[1], args[2]);
        if (rc != Gwpack.OK) {
            throw new IllegalStateException("configure failed: " + Gwpack.lastError(lib));
        }

        byte[] firmware = Files.readAllBytes(Path.of(args[3]));
        byte[] pkg = Gwpack.servicePack(lib, firmware, "gateway", "java example");
        String filename = Gwpack.serviceLastFilename(lib);
        Files.write(Path.of(filename), pkg);
        System.out.println("service pack: " + pkg.length + " bytes, filename=" + filename);
    }
}

编与跑(在 tool/native/java/ 下;先下 JNA 5.14.0,库路径指到编出的原生库):

  1. curl -fsSL -o jna-5.14.0.jar https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar
  2. javac -cp jna-5.14.0.jar:. Gwpack.java GwpackExample.java
  3. java -Djna.library.path=../../../target/release -cp jna-5.14.0.jar:. GwpackExample http://127.0.0.1:3009 <api-key> <api-secret> <firmware-file>

预期:打印库版本、打包字节数与服务给的文件名,包写到当前目录、文件名与打印的一致。服务演示(GwpackServiceDemo.java)是另一条路:纯 Java 直调 HTTP,不用原生库、不用 JNA(签名串 7 字段,不带设备密钥,见服务检查文档 4.2 节)。

通过标准与失败处理

命令速查

# 编译(原生库随 workspace 编出)
cargo build --release --workspace

# C 动态加载服务检查(用 sign-server 已起好的服务和凭证)
gcc -o /tmp/c_dlopen_test tool/native/ffi-test/c_dlopen_test.c -ldl
/tmp/c_dlopen_test ./target/release/libsign_tool.so http://127.0.0.1:3009 "<key>" "<secret>"

# Java 示例(在 tool/native/java/ 下,先下 JNA,库路径指到编出的原生库)
curl -fsSL -o jna-5.14.0.jar https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar
javac -cp jna-5.14.0.jar:. Gwpack.java GwpackExample.java
java -Djna.library.path=../../../target/release -cp jna-5.14.0.jar:. GwpackExample http://127.0.0.1:3009 <api-key> <api-secret> <firmware-file>