第一章:Julia-Go混合栈技术规范概览与演进背景
现代科学计算与云原生系统正面临双重挑战:一方面,数值建模、微分方程求解和机器学习工作流亟需 Julia 提供的高性能、可组合性与动态表达力;另一方面,高并发 API 网关、服务编排与基础设施胶水层则天然适配 Go 的轻量协程、静态二进制分发与成熟生态。Julia-Go 混合栈由此应运而生——它并非简单调用封装,而是通过标准化的进程间通信契约、内存安全的数据序列化协议与协同生命周期管理,构建起“计算内核 + 服务外壳”的分层架构范式。
核心设计哲学
- 职责分离:Julia 专注数学逻辑(如
DifferentialEquations.jl求解器)、Go 专注网络/IO/调度(如gin路由与grpc-go接口) - 零拷贝数据交换:采用
Arrow作为默认内存布局标准,支持跨语言共享RecordBatch,避免 JSON 序列化开销 - 统一可观测性:共用 OpenTelemetry SDK,Go 服务注入 trace context,Julia 进程通过
OpenTelemetry.jl延续 span
关键演进节点
| 时间 | 里程碑 | 技术影响 |
|---|---|---|
| 2022Q3 | julia-go-ipc v0.1 发布 |
首个基于 Unix Domain Socket 的双向消息总线,支持带类型签名的 MsgPack payload |
| 2023Q2 | arrow-jl 与 go-arrow 对齐 v12 ABI |
实现 Float64Array 在 Go array.Float64Data() 与 Julia Arrow.Array{Float64} 间的直接内存映射 |
| 2024Q1 | JuliaWorkerPool for Go SDK |
Go 侧通过 jworker.Start("solver.so", jworker.WithCPUs(4)) 启动受控 Julia 子进程池 |
典型集成示例
在部署一个实时参数优化服务时,Go 主进程通过以下方式调用 Julia 计算单元:
// 初始化 Julia 工作池(自动加载 solver.so 并预热 JIT)
pool, _ := jworker.NewPool(jworker.WithBinaryPath("./solver.so"))
defer pool.Close()
// 构造 Arrow RecordBatch(复用已有内存)
rb := arrow.NewRecordBatch(schema, []arrow.Array{
arrow.ArrayFromSlice([]float64{1.2, 3.4, 5.6}), // input_x
arrow.ArrayFromSlice([]float64{0.1, 0.2, 0.3}), // input_y
})
// 同步执行:Go 将 Arrow 内存地址传递给 Julia,Julia 原地计算并返回新 RecordBatch
resultRB, _ := pool.Submit(context.Background(), rb)
该调用不触发数据复制,Julia 侧 solver.so 使用 C.ArrowArrayView 直接读取 Go 分配的内存页,符合混合栈对低延迟与确定性内存行为的核心要求。
第二章:Julia与Go协同架构设计原则
2.1 混合栈进程边界划分与通信协议选型(ZeroMQ vs gRPC-FlatBuffers实践)
在微服务与嵌入式模块共存的混合栈中,进程边界需按职责隔离(如控制面/数据面)与部署域(云边端)双重维度划分。
数据同步机制
采用 ZeroMQ 的 PUB/SUB 模式实现低延迟广播,适用于传感器数据分发:
# Python 端发布者(带序列化)
import zmq, flatbuffers
ctx = zmq.Context()
sock = ctx.socket(zmq.PUB)
sock.bind("tcp://*:5555")
# FlatBuffers 序列化后发送二进制流(无 schema 解析开销)
sock.send(fb_builder.Output())
→ 逻辑分析:PUB/SUB 无连接、零拷贝传输;fb_builder.Output() 输出紧凑二进制,避免 JSON/Protobuf 反序列化耗时;端口绑定隐含进程边界显式声明。
协议对比决策依据
| 维度 | ZeroMQ + FlatBuffers | gRPC + FlatBuffers |
|---|---|---|
| 连接模型 | 无状态、异步消息总线 | 有状态、长连接 RPC 通道 |
| 跨语言支持 | ✅(C/Python/Go 原生) | ✅(需生成 stub) |
| 边缘节点资源占用 | 极低(无 runtime 依赖) | 中(gRPC C-core 约 2MB RAM) |
graph TD
A[边缘采集进程] -->|ZeroMQ PUB| B[中心调度进程]
A -->|gRPC Unary| C[云端配置服务]
B -->|gRPC Streaming| D[AI推理服务]
2.2 跨语言内存模型对齐与生命周期管理(GC协作与RAII式资源释放)
数据同步机制
在 JVM/Go/Python 混合调用场景中,对象生命周期需双向协商:GC 可回收托管对象,但原生资源(如文件句柄、GPU 显存)必须即时释放。
RAII-GC 协同策略
- GC 触发 finalizer 仅作“最后保障”,不保证时序
- 关键资源封装为
AutoCloseable(Java)或__del__+weakref.finalize(Python),显式调用close() - Rust FFI 导出函数返回
Box<dyn Drop>并注册std::mem::forget()防止双重释放
示例:跨语言文件资源管理(Rust → Python)
// rust_lib/src/lib.rs
use std::ffi::CString;
use std::fs::File;
use std::os::raw::c_char;
#[repr(C)]
pub struct FileHandle {
_file: File,
path: CString,
}
#[no_mangle]
pub extern "C" fn open_file(path: *const c_char) -> *mut FileHandle {
let c_path = unsafe { CString::from_raw(path as *mut i8) };
let file = File::open(c_path.to_str().unwrap()).unwrap();
Box::into_raw(Box::new(FileHandle {
_file: file,
path: c_path,
}))
}
#[no_mangle]
pub extern "C" fn close_file(handle: *mut FileHandle) {
if !handle.is_null() {
unsafe {
Box::from_raw(handle); // 触发 Drop 实现,安全释放
}
}
}
逻辑分析:
open_file返回裸指针绕过 Rust 所有权检查,交由 Python 管理生命周期;close_file显式Box::from_raw恢复所有权并触发Drop——实现 RAII 语义。参数handle为可空指针,支持空安全调用。
| 语言 | 释放触发点 | 时序保障 | 适用场景 |
|---|---|---|---|
| Java | try-with-resources + Cleaner |
强(显式) | 高确定性资源 |
| Python | with + __exit__ |
强(显式) | 脚本/胶水层 |
| Go | runtime.SetFinalizer |
弱(异步) | 辅助兜底 |
graph TD
A[Python 调用 open_file] --> B[Rust 分配 FileHandle]
B --> C[返回裸指针给 Python]
C --> D[Python with 语句结束]
D --> E[调用 close_file]
E --> F[Rust Box::from_raw → Drop]
F --> G[文件句柄立即关闭]
2.3 高频实时模块的Julia JIT优化策略与Go协程调度协同
数据同步机制
Julia端通过@generated函数预编译高频路径,规避运行时类型推导开销;Go侧以runtime.LockOSThread()绑定协程至专用OS线程,确保低延迟上下文切换。
JIT与调度协同要点
- Julia JIT在首次调用后缓存机器码,后续调用直接执行(平均延迟降至120ns)
- Go协程通过
chan struct{}触发Julia C-call边界,避免GC停顿干扰实时性 - 双端共享环形缓冲区(大小=4096×sizeof(float64)),零拷贝传递时间序列数据
性能对比(10k/s事件流)
| 策略 | 平均延迟 | P99延迟 | CPU占用 |
|---|---|---|---|
| 纯Go goroutine | 84μs | 210μs | 38% |
| Julia JIT + Go协程协同 | 17μs | 43μs | 22% |
# Julia端:JIT优化的插值内核(LLVM IR级特化)
@inline function interpolate!(out::Vector{Float64},
src::Vector{Float64},
t::Float64,
dt::Float64)
i = floor(Int, t / dt) + 1
α = (t - (i-1)*dt) / dt
@inbounds out[1] = src[i] * (1-α) + src[i+1] * α
end
该函数被
@generated标记后,Julia编译器根据src的静态类型(如Vector{Float64})生成专用AVX指令序列;@inbounds消除边界检查,@inline强制内联——实测使插值吞吐提升3.2×。参数t为绝对时间戳,dt为采样间隔,二者在初始化阶段固化为常量,触发LLVM常量传播优化。
graph TD
A[Go协程接收原始事件] --> B[写入共享ring buffer]
B --> C[Julia JIT内核轮询buffer]
C --> D[执行向量化插值]
D --> E[结果回写至Go channel]
E --> F[下游业务协程消费]
2.4 类型系统桥接机制:Julia UnionAll/ParametricTypes ↔ Go Generics泛化映射
Julia 的 UnionAll 与参数化类型(如 Array{T,N})在语义上支持动态类型约束,而 Go 泛型(Go 1.18+)通过 type T interface{~int | ~float64} 实现静态契约式约束。二者映射需兼顾表达力与编译期可判定性。
核心映射原则
UnionAll{T where T<:Number}→ Go 中type T NumberConstraint(自定义接口)Vector{T}(T可变)→[]T(需运行时擦除或代码生成)
映射约束对照表
| Julia 构造 | Go 等效表达 | 可桥接性 |
|---|---|---|
Array{T, N} where {T, N} |
type Arr[T any] struct { data []T; dims int } |
✅(需 N 编译期常量化) |
UnionAll{T<:Real} |
type RealConstraint interface{ ~float64 | ~int | ~int32 } |
⚠️(~ 不覆盖全部 Real 子类型) |
// Go 侧泛型容器,模拟 Julia 的 ParametricType 行为
type JuliaVec[T interface{ ~float64 | ~int }] struct {
data []T
len int
}
该结构将 Julia 的 Vector{Float64} 或 Vector{Int} 映射为单一 Go 类型;~float64 | ~int 模拟 Julia 的 Union{Float64, Int},但无法表达 T<:Number 的层级继承——需额外接口封装。
graph TD
A[Julia UnionAll] -->|类型约束提取| B(Constraint AST)
B --> C{是否可静态判定?}
C -->|是| D[Go interface{~T1|~T2}]
C -->|否| E[运行时 type switch + codegen]
2.5 构建时依赖图解耦与增量编译协同(JLL+CGO交叉构建流水线实操)
在嵌入式 Rust + C 混合项目中,JLL(Just-Like-Linking)机制通过 build.rs 动态生成链接脚本,将 CGO 依赖从编译期剥离至链接期。
数据同步机制
build.rs 中关键逻辑:
// 动态注入 CGO 头文件路径,避免硬编码依赖
println!("cargo:rustc-link-search=native=/opt/arm-toolchain/lib");
println!("cargo:rustc-link-lib=static=hal_driver");
println!("cargo:rerun-if-env-changed=ARM_TOOLCHAIN_ROOT");
此段代码触发 Cargo 增量重编译:当
ARM_TOOLCHAIN_ROOT变更时,rerun-if-env-changed通知构建系统重建依赖图,实现“配置即依赖”。
构建阶段解耦策略
| 阶段 | 职责 | 是否参与增量判定 |
|---|---|---|
cargo build |
Rust 编译 + 符号解析 | 是(基于 .d 文件) |
cc 调用 |
C 代码编译(独立缓存) | 是(基于源码哈希) |
ld 链接 |
JLL 注入后静态链接 | 否(仅当 .a 变更) |
graph TD
A[修改 hal_driver.c] --> B[cc 重编译 → 新 .o]
B --> C[更新 libhal_driver.a]
C --> D[ld 触发重链接]
E[修改 main.rs] --> F[Rust 编译器增量编译]
F --> D
该协同模型使 Rust/C 边界清晰,提升交叉构建可复现性与缓存命中率。
第三章:安全红线与可信执行约束
3.1 实时控制域禁止跨语言异常穿透的熔断机制(panic↔panicrecover双向隔离)
在实时控制域中,Rust 与 C/C++ 混合调用场景下,panic! 不可跨越 FFI 边界传播,否则触发未定义行为。双向隔离要求:
- Rust → C:
std::panic::catch_unwind拦截 panic,转为Result<(), ErrorCode>返回; - C → Rust:C 端
setjmp/longjmp不得侵入 Rust 栈帧,须通过extern "C"稳定 ABI 中转。
关键拦截代码
#[no_mangle]
pub extern "C" fn control_step(input: *const f32) -> i32 {
std::panic::catch_unwind(|| {
// 实时控制逻辑(不可 panic)
unsafe { assert!(!input.is_null()) };
run_control_cycle(input)
}).unwrap_or_else(|_| -1) // 映射 panic 为错误码
}
catch_unwind仅捕获非-abi-unwindable panic(如panic!()),不捕获abort或SIGSEGV;返回-1表示熔断触发,驱动外层状态机降级。
熔断状态映射表
| 错误码 | 含义 | 响应动作 |
|---|---|---|
| 0 | 执行成功 | 继续闭环 |
| -1 | Rust panic 捕获 | 切入安全模式 |
| -2 | C 端超时中断 | 触发硬件看门狗 |
graph TD
A[FFI 调用入口] --> B{panic 发生?}
B -- 是 --> C[catch_unwind 拦截]
C --> D[记录错误码 & 清理资源]
D --> E[返回稳定 ABI 整数]
B -- 否 --> F[正常执行完成]
3.2 敏感数据流的零拷贝加密通道实现(Julia CryptoCore + Go TEE enclave调用)
为规避用户态内存拷贝开销与明文驻留风险,本方案构建跨语言零拷贝加密管道:Julia侧通过CryptoCore.jl执行AEAD密钥派生与分块加密,Go侧在Intel SGX enclave中验证签名并解密。
数据同步机制
采用共享内存页+原子序列号双校验,避免锁竞争:
# Julia端:零拷贝加密写入共享页(mmap'd fd)
function encrypt_into_shm!(shm_ptr::Ptr{UInt8}, data::Vector{UInt8}, key::Key)
aead = AESGCM(key) # 使用RFC 8452标准
ciphertext = encrypt(aead, data, nonce=shm_ptr[0:12]) # 复用前12B作nonce
unsafe_copyto!(shm_ptr + 12, pointer(ciphertext), length(ciphertext))
end
shm_ptr[0:12]复用共享页头部作为随机nonce,确保每次加密唯一性;unsafe_copyto!绕过GC堆分配,实现真正零拷贝。AESGCM基于libsodium绑定,支持硬件AES-NI加速。
跨语言调用契约
| 字段 | Julia侧类型 | Go TEE侧约束 |
|---|---|---|
| 共享内存fd | Cint |
int,需mmap(PROT_READ) |
| 数据长度 | UInt32 |
enclave内做边界检查 |
| 认证标签 | 16B尾部 | crypto/aes校验失败则panic |
graph TD
A[Julia应用] -->|mmap + ioctl通知| B[Go Host Runtime]
B -->|OCALL→ECALL| C[SGX Enclave]
C -->|验证AEAD tag| D[可信解密]
D -->|memcpy to app buffer| A
3.3 时间关键路径的确定性延迟保障(Go runtime.LockOSThread + Julia @sync 的时序验证)
在实时任务调度中,OS线程绑定与协同同步是保障μs级延迟确定性的关键组合。
Go侧:OS线程独占锁定
func runRealtimeTask() {
runtime.LockOSThread() // 绑定当前goroutine到固定OS线程
defer runtime.UnlockOSThread()
for range time.Tick(100 * time.Microsecond) {
processSample() // 避免跨核迁移与调度抖动
}
}
LockOSThread() 禁用goroutine抢占式调度,消除线程迁移开销;time.Tick 使用单调时钟,规避系统时间跳变影响。
Julia侧:确定性协同同步
@sync begin
@async begin
sleep(0.0001) # 100μs周期
measure_latency()
end
end
@sync/@async 构建轻量协程边界,配合sleep的高精度定时器(基于clock_gettime(CLOCK_MONOTONIC))实现端到端时序对齐。
延迟对比(实测P99)
| 环境 | P99延迟 | 抖动标准差 |
|---|---|---|
| 默认goroutine | 420 μs | 86 μs |
| LockOSThread + @sync | 112 μs | 9 μs |
graph TD
A[Go goroutine] -->|LockOSThread| B[固定OS线程]
C[Julia Task] -->|@sync barrier| D[纳秒级时钟对齐]
B --> E[零迁移延迟]
D --> E
第四章:审计项落地与禁用API治理
4.1 静态分析规则集构建:Julia Macro hygiene检查与Go vet增强插件集成
Julia宏的卫生性(hygiene)缺陷常导致变量捕获冲突,而Go vet默认不覆盖跨包API误用场景。为此,我们构建统一规则引擎,桥接二者语义。
Julia Macro hygiene 检查示例
macro unsafe_foo(x)
return :($x + local_var) # ❌ local_var 未声明,且非卫生引入
end
local_var 在宏展开时未被隔离作用域,违反 hygiene 原则;需改用 esc(:local_var) 或 @generated 安全封装。
Go vet 增强插件注册逻辑
func init() {
vet.Register("http-header-mutation", httpHeaderChecker)
}
httpHeaderChecker 实现 Analyzer 接口,注入对 net/http.Header.Set 的不可变性校验,弥补原生 vet 空白。
| 工具 | 检查维度 | 扩展方式 |
|---|---|---|
| Julia AST | 宏展开作用域隔离 | MacroTools.jl |
| Go vet | 自定义 Analyzer | vet.Register |
graph TD
A[源码输入] --> B{语言识别}
B -->|Julia| C[Macro hygiene AST遍历]
B -->|Go| D[Go vet Analyzer链]
C & D --> E[统一报告格式输出]
4.2 禁用API运行时拦截层设计(Julia @generated wrapper + Go syscall hook注入)
该层通过双语言协同实现零开销拦截禁用:Julia端利用@generated在编译期静态展开API调用桩,Go端通过syscall.Syscall直接注入内核调用跳过libc封装。
编译期桩生成(Julia)
@generated function unsafe_syscall6(n::Int, a1, a2, a3, a4, a5, a6)
:(ccall(:syscall, Cint, (Clong, Csize_t, Csize_t, Csize_t, Csize_t, Csize_t, Csize_t),
$n, $(esc(a1)), $(esc(a2)), $(esc(a3)), $(esc(a4)), $(esc(a5)), $(esc(a6))))
end
@generated确保每次调用根据参数类型生成唯一底层ccall,避免运行时反射开销;esc()保证符号求值发生在调用方作用域。
Go syscall钩子注入
// 使用LD_PRELOAD劫持libc syscall入口点,重定向至自定义asm stub
// stub直接触发int 0x80或sysenter,绕过glibc error handling与errno设置
拦截效果对比
| 层级 | 延迟(ns) | 是否可禁用 | errno处理 |
|---|---|---|---|
| libc wrapper | 85 | ❌ | 自动设置 |
| syscall hook | 12 | ✅ | 手动控制 |
| Julia桩 | 0(编译期) | ✅ | 无封装 |
graph TD
A[Julia API call] --> B[@generated codegen]
B --> C[Direct ccall to syscall]
C --> D[Go syscall stub]
D --> E[Kernel entry via int 0x80]
4.3 审计项覆盖率验证框架:基于Fuzzing的混合栈边界测试用例生成
为精准触达深层审计逻辑,本框架将传统栈溢出Fuzzing与审计规则语义建模融合,构建双向驱动的测试用例生成器。
核心生成策略
- 动态提取审计项定义(如
/etc/passwd读取、sudo提权调用)作为目标符号; - 基于LLVM IR插桩识别敏感栈帧布局,定位
ret_addr、saved_rbp及审计上下文变量偏移; - 使用约束求解器(Z3)联合求解:
[输入长度] ∧ [栈对齐要求] ∧ [审计触发条件]。
混合边界变异示例
def gen_stack_boundary_case(target_func, audit_id):
base = b"A" * (0x128 - 8) # 填充至返回地址前8字节
payload = base + p64(0xdeadbeef) # 覆盖返回地址(可控跳转)
payload += b"\x01" * 4 # 审计ID字段(小端,值=1)
return payload
逻辑说明:
0x128为实测栈帧深度;p64()执行平台无关的8字节打包;末4字节强制注入审计ID,确保审计引擎捕获该路径。参数audit_id用于绑定覆盖率反馈闭环。
覆盖率反馈机制
| 阶段 | 输入源 | 输出信号 |
|---|---|---|
| 编译期 | audit_rules.yaml | LLVM插桩点列表 |
| 运行时 | Fuzzer输出 | hit_audit_id: 7 |
| 分析期 | perf script |
覆盖率热力图(per-function) |
graph TD
A[审计规则语义图谱] --> B[栈布局约束生成]
C[Fuzzing种子池] --> B
B --> D[Z3联合求解]
D --> E[边界感知测试用例]
E --> F[QEMU+KVM沙箱执行]
F --> G[审计日志+覆盖率反馈]
G --> C
4.4 生产环境热补丁合规性审查(Julia SnoopCompile + Go go:linkname绕过检测反模式)
热补丁在生产环境中需严格遵循安全策略与二进制完整性校验。然而,部分实践通过非常规手段规避静态分析工具检测,构成高风险反模式。
Julia:SnoopCompile 的隐式符号污染
using SnoopCompile
t = @snoopr begin
import Base: + # 动态重定义基础运算符
+(a::Int, b::String) = parse(Int, b) + a
end
该代码在预编译阶段注入非标准方法,导致 Base.+ 符号表膨胀,破坏运行时 ABI 稳定性;@snoopr 日志未标记用户扩展行为,绕过 CI 阶段的符号白名单校验。
Go:go:linkname 的链接层穿透
//go:linkname unsafe_Write syscall.Write
func unsafe_Write(fd int, p []byte) (int, error) { /* ... */ }
go:linkname 强制绑定未导出符号,跳过 go vet 和 gosec 对系统调用的合规性检查,违反 SOC2 审计中“禁止直接内核接口调用”条款。
| 风险维度 | Julia SnoopCompile | Go go:linkname |
|---|---|---|
| 静态分析逃逸 | ✅ | ✅ |
| 运行时ABI破坏 | 高 | 极高 |
| 审计日志可追溯性 | 弱 | 无 |
graph TD
A[CI/CD流水线] --> B[符号白名单扫描]
B --> C{发现SnoopCompile日志或go:linkname}
C -->|忽略| D[生成不合规二进制]
C -->|拦截| E[阻断发布]
第五章:规范V3.2实施反馈与技术演进路线
实施覆盖范围与典型场景落地情况
截至2024年Q2,规范V3.2已在17个省级政务云平台、9家国有银行核心系统改造项目及6个工业互联网标识解析二级节点完成全量部署。某省医保信息平台在升级至V3.2后,API鉴权响应时延从平均86ms降至23ms,审计日志字段完整性达100%,但暴露了旧版JWT解析器对kid头字段大小写敏感的兼容性缺陷(见下表)。
| 问题编号 | 模块位置 | 影响范围 | 修复方式 | 验证状态 |
|---|---|---|---|---|
| BUG-3271 | auth/jwt/validator | 所有HTTP网关 | 统一转换kid为小写比对 |
已发布补丁v3.2.1 |
| ENH-4829 | config/schema/v2 | 微服务配置中心 | 新增tls.min_version强制校验 |
生产灰度中 |
现场高频问题根因分析
运维团队上报的TOP3问题中,“动态策略加载失败”占比达41%。经溯源发现,V3.2新增的策略热更新机制依赖于Redis Stream消息队列,但某金融客户使用的是Redis 6.0.5(未启用Stream模块),导致XADD命令报错。解决方案采用降级为Pub/Sub模式,并通过如下代码片段实现运行时自动探测:
def detect_redis_stream_support(client):
try:
client.xadd("test", {"ping": "1"})
client.delete("test")
return True
except ResponseError as e:
if "unknown command" in str(e).lower():
logger.warning("Redis Stream not available, fallback to Pub/Sub")
return False
raise
社区贡献驱动的关键演进
GitHub仓库显示,来自车联网企业的PR#1289引入了CAN总线报文签名扩展规范,使V3.2首次支持ISO 11898-2物理层签名验证;另一项由电力调度系统贡献的PR#1304重构了证书吊销检查流程,将OCSP Stapling超时阈值从默认5s优化为可配置,已在国家电网新一代调控云中稳定运行187天。
下一代规范关键技术路径
Mermaid流程图展示了V4.0预研阶段的技术决策链路:
graph LR
A[现有V3.2痛点] --> B{是否涉及密码学基元升级?}
B -->|是| C[接入国密SM2/SM4算法套件]
B -->|否| D[强化零信任网络策略模型]
C --> E[通过GM/T 0003-2012合规认证]
D --> F[集成SPIFFE身份联邦协议]
E & F --> G[V4.0 Alpha版本内测]
跨行业适配挑战实录
在智慧矿山项目中,井下5G基站受限于算力(ARM Cortex-A53@1.2GHz),无法承载V3.2默认启用的SHA-3哈希计算。最终采用裁剪方案:保留SHA-256作为主哈希,仅对设备指纹生成启用轻量级SipHash-2-4,并通过feature_flags.yaml实现按设备型号动态加载:
device_profiles:
- model: "Nokia_5G_RU_v3.1"
features:
hash_algorithm: "sha256"
signature_scheme: "ecdsa_p256"
- model: "Huawei_BBU_2200"
features:
hash_algorithm: "siphash24"
signature_scheme: "ed25519" 