| 项目 | 内容 |
|---|---|
| 适用 | 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 。
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 取 |
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 之前调。
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 取,不走原生库。
int last_filename(char *buf, size_t buf_len);
参数:
| 参数 | 说明 |
|---|---|
| buf / buf_len | 调用方缓冲区与长度;buf 为空指针或 buf_len 为 0 时是问长度,不写字节 |
返回:文件名的完整长度(不含 NUL),所有情况下都返回完整长度;缓冲区太小则写截断后的 NUL 结尾副本,调用方拿返回值与 buf_len 比较可发现截断;本线程没成功打包过返回 0(空串)。
int last_error(char *buf, size_t buf_len);
参数:同上(缓冲区与长度;空指针或长度 0 问长度)。
返回:同上协议;长度 0 表示本线程上次调用没失败,或还没调过。
const char *lib_version(void);
参数:无。
返回:库版本号字符串指针(当前 0.1.0);指针永久有效,不用释放、不要 free;保证非空。
服务接口的三项测试(pack_without_configure_fails、configure_rejects_bad_inputs、lib_version_is_non_empty:未配置拒收、非法配置拒收、版本号非空)已随库单测跑过(不用联网、不用起服务),这里不再重跑。
tool/native/ffi-test/c_dlopen_test.c:不链接库,运行时 dlopen 加载,依次验:版本号非空、未配置打包拒收、空 key 配置拒收、配好后对真服务打包(8192 字节测试固件,包 8520 字节、包头 GWPK + 格式 3)、服务给的文件名非空且以 .pkg 结尾。证明导出的 C 服务接口经真实动态加载可用。完整验证(签名与解密往返)不在这里,由 Rust 服务示例与命令行验包步骤覆盖。
在持续集成里最后跑(同款);本地跑时用 sign-server 已起好的服务和凭证,不要另起服务:
逐行跑(行内代码点一下就复制):
cargo build --release --workspacegcc -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>"预期:打印库版本、打包 8520 字节、服务给的文件名,末行 ALL C DLOPEN CHECKS PASSED,退出 0。
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 自动加平台前后缀)。
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,库路径指到编出的原生库):
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.jarjavac -cp jna-5.14.0.jar:. Gwpack.java GwpackExample.javajava -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>