f基于Android13中Google开源的DiceService(Google在尝试将用rust实现新组建,由于没有好的切入点,逮着一个先看),Dice是做什么的我还是处于懵逼中,我们先就HAL Service来说,先看看Google如何将Rust语言集成到Android系统当中。
AIDL ModuleDevice
注:如果您对 AIDL有任何困惑请移步source.google.android.cn。
@SensitiveData @VintfStability
interface IDiceDevice {
android.hardware.security.dice.Signature sign(in android.hardware.security.dice.InputValues[] id, in byte[] payload);
android.hardware.security.dice.Bcc getAttestationChain(in android.hardware.security.dice.InputValues[] inputValues);
android.hardware.security.dice.BccHandover derive(in android.hardware.security.dice.InputValues[] inputValues);
void demote(in android.hardware.security.dice.InputValues[] inputValues);
}
这个接口描述了HAL的功能,字面上看Dice服务带了四个函数。
- sign
- getAttestationChain
- derive
- demote
基于对HAL层的架构规定,如果是C/C++的实现,我们非常清楚任何HAL的实现形式就是像HAL中添加一个可以被扫描的符号HAL_MODULE_INFO_SYM,比如:
struct hw_module_t HAL_MODULE_INFO_SYM = {
.tag = HARDWARE_MODULE_TAG,
.module_api_version = 1,
.hal_api_version = 0,
.id = GPS_HARDWARE_MODULE_ID,
.name = "loc_api GPS Module",
.author = "Qualcomm USA, Inc.",
.methods = &gps_module_methods,
};
这里的每个字段都是为了提供有用的信息以保证native能够通过这些信息维持接口的宽容度。对于不同版本的hal,差异将体现在methods的open函数指针中。
static struct hw_module_methods_t gps_module_methods = {
.open = open_gps
};
回顾hidl,Legacy HAL需要用经过抽象的 HIDLlib进行封装,对于暴露的interface, 需要特定的符号(嗯,不用怀疑,就是同一个套路)。比如audio的HIDL抽象:
IDevicesFactory* HIDL_FETCH_IDevicesFactory(const char* name) {
return strcmp(name, "default") == 0 ? new DevicesFactory() : nullptr;
}
经过框架的魔法加持,现在你可以将IDeviceFactory暴露给HIDL服务:
/**
argument first is HIDL lib name, which is made of 3 part
<module>@<version>::<IInterfaceName>, for EX.
"android.hardware.audio@7.1::IDevicesFactory"
*/
registerPassthroughServiceImplementation(*first)
关于HIDL的黑魔法实现,我至今还没有找到有人针对libhidl和libhwbinder进行系统的分析,如果你要搞清楚这些事情,我认为你必须搞清楚hidl-gen是如何解析HAL并生成中间文件的,当然,这里我们已经清楚了open是怎样调用的,如果没有HIDL,就是通过HAL的接口直接调用符合method的open,如果有HIDL,则通过HIDL抽象库进行调用。
打住!现在来看看rust是如何做的。由于Dice服务是通过AIDL注册,这里不得不吐槽了,你看不到之前的任何影子,说明什么呢,基于C语言的.h规约的遗产HAL接口可以解放了,值得庆幸。
重新来过,先从Service服务入口看起
use anyhow::Result;
use diced::{
dice,
hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
};
use diced_sample_inputs::make_sample_bcc_and_cdis;
use serde::{Deserialize, Serialize};
use std::convert::TryInto;
use std::panic;
use std::sync::Arc;
static DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
#[derive(Debug, Serialize, Deserialize, Clone)]
struct InsecureSerializableArtifacts {
cdi_attest: [u8; dice::CDI_SIZE],
cdi_seal: [u8; dice::CDI_SIZE],
bcc: Vec<u8>,
}
impl DiceArtifacts for InsecureSerializableArtifacts {
fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
&self.cdi_attest
}
fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
&self.cdi_seal
}
fn bcc(&self) -> Vec<u8> {
self.bcc.clone()
}
}
impl UpdatableDiceArtifacts for InsecureSerializableArtifacts {
fn with_artifacts<F, T>(&self, f: F) -> Result<T>
where
F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
{
f(self)
}
fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
Ok(Self {
cdi_attest: *new_artifacts.cdi_attest(),
cdi_seal: *new_artifacts.cdi_seal(),
bcc: new_artifacts.bcc(),
})
}
}
fn main() {
android_logger::init_once(
android_logger::Config::default()
.with_tag("android.hardware.security.dice")
.with_min_level(log::Level::Debug),
);
// Redirect panic messages to logcat.
panic::set_hook(Box::new(|panic_info| {
log::error!("{}", panic_info);
}));
// Saying hi.
log::info!("android.hardware.security.dice is starting.");
let (cdi_attest, cdi_seal, bcc) =
make_sample_bcc_and_cdis().expect("Failed to construct sample dice chain.");
let hal_impl = Arc::new(
unsafe {
// Safety: ResidentHal cannot be used in multi threaded processes.
// This service does not start a thread pool. The main thread is the only thread
// joining the thread pool, thereby keeping the process single threaded.
ResidentHal::new(InsecureSerializableArtifacts {
cdi_attest: cdi_attest[..]
.try_into()
.expect("Failed to convert cdi_attest to array reference."),
cdi_seal: cdi_seal[..]
.try_into()
.expect("Failed to convert cdi_seal to array reference."),
bcc,
})
}
.expect("Failed to create ResidentHal implementation."),
);
let hal = DiceDevice::new_as_binder(hal_impl).expect("Failed to construct hal service.");
binder::add_service(DICE_HAL_SERVICE_NAME, hal.as_binder())
.expect("Failed to register IDiceDevice Service");
log::info!("Joining thread pool now.");
binder::ProcessState::join_thread_pool();
}
这个服务的代码已经给我展示了足够多的信息以支撑你完成一个HAL的基本开发。首先,log模块需要手动初始化,同时可以转写panic信息到logcat:
关于android在rust上实现的基础组建,可以去external/rust/crates目录查看
android_logger::init_once(
android_logger::Config::default()
.with_tag("android.hardware.security.dice")
.with_min_level(log::Level::Debug),
);
// Redirect panic messages to logcat.
panic::set_hook(Box::new(|panic_info| {
log::error!("{}", panic_info);
}));
接下来这个let 语句创建了一个比较复杂的封装对象,第一层Arc是线程间可交换的智能指针,whatever就当作带了锁的shared_ptr好了,内部嵌套了一个unsafe并给出了unsafe的理由是这个HAL不能进行多线程通信,因此主进程注册好service后将挂在join_thread_pool,ResidentHAL会For一个子进程并建立内部的管道通信。好的,hal_impl实际上就是ResidentHAL对象,并在构建时传入了这个InsecureSerializableArtifacts。
业务代码放在一边,我们聚焦在下面这句let hal。这实际上是一种方式,将业务对象和binder服务对象联系起来,binder服务是 BnBinder,需要一个Impl来实现adil提供的接口,因此这个DiceDevice就是一个IDiceDevice Traits的实现,作为一个proxy,正在执行业务的类hal_impl被注册到了服务中:
pub struct DiceDevice {
hal_impl: Arc<dyn DiceHalImpl + Sync + Send>,
}
impl DiceDevice {
/// Constructs an instance of DiceDevice, wraps it with a BnDiceDevice object and
/// returns a strong pointer to the binder. The result can be used to register
/// the service with service manager.
pub fn new_as_binder(
hal_impl: Arc<dyn DiceHalImpl + Sync + Send>,
) -> Result<Strong<dyn IDiceDevice>> {
let result = BnDiceDevice::new_binder(DiceDevice { hal_impl }, BinderFeatures::default());
Ok(result)
}
}
impl binder::Interface for DiceDevice {}
impl IDiceDevice for DiceDevice {
fn sign(&self, input_values: &[BinderInputValues], message: &[u8]) -> BinderResult<Signature> {
map_or_log_err(self.hal_impl.sign(input_values, message), Ok)
}
fn getAttestationChain(&self, input_values: &[BinderInputValues]) -> BinderResult<Bcc> {
map_or_log_err(self.hal_impl.get_attestation_chain(input_values), Ok)
}
fn derive(&self, input_values: &[BinderInputValues]) -> BinderResult<BccHandover> {
map_or_log_err(self.hal_impl.derive(input_values), Ok)
}
fn demote(&self, input_values: &[BinderInputValues]) -> BinderResult<()> {
map_or_log_err(self.hal_impl.demote(input_values), Ok)
}
}
最后,将业务的接口注册给binder:Binder::add_service(DICE_HAL_SERVICE_NAME, hal.as_binder()),这里你可能会发现 DiceDevice并没有实现一个叫as_binder的方法,如果你自己注意到了,我想你真的认真的看了本贴,我也非常的开心。答案呢就在BnDiceDevice::new_binder {hal_impl}. 当然,这些代码都是框架为你生成的,也就是文章最开头的aidl,在其编译器由解释器为你生成了这些代码,比如as_binder的实现(Android.bp-> aidl_interface):
*code_writer << "fn as_binder(&self) -> binder::SpIBinder { self._inner.as_binder() }\n";
pub fn add_service(identifier: &str, mut binder: SpIBinder) -> Result<()> {
let instance = CString::new(identifier).unwrap();
let status = unsafe {
// Safety: `AServiceManager_addService` expects valid `AIBinder` and C
// string pointers. Caller retains ownership of both
// pointers. `AServiceManager_addService` creates a new strong reference
// and copies the string, so both pointers need only be valid until the
// call returns.
sys::AServiceManager_addService(binder.as_native_mut(), instance.as_ptr())
};
status_result(status)
}
注册hal的service与audio的hal并没有什么不同,最终还是调用cpp的方法:
binder_exception_t AServiceManager_addService(AIBinder* binder, const char* instance) {
if (binder == nullptr || instance == nullptr) {
return EX_ILLEGAL_ARGUMENT;
}
sp<IServiceManager> sm = defaultServiceManager();
status_t exception = sm->addService(String16(instance), binder->getBinder());
return PruneException(exception);
}
总结:我们要实现自己的服务,其实没有想象中那么难,也没有什么魔法,只要想清楚2件事:
1. 我们要实现服务用来做什么,写出接口
2. 现在的Android框架下能否实现,如何实现
使用rust,你会面临着生命周期地狱,其难度甚至不比cpp的抽象来的更容易。当我们面对binder服务时,到处充斥着proxy模式,除了C语言,其他任何语言的封装都给我们的阅读造成了一定程度的破坏比如hidl_gen, aidl_gen等解释工具,它很强大,可以为你生成BE端语言的lib包。相应的,你需要非常大的成本来找到并消化掉它到底提供了什么便捷的接口给你使用,所以一般我们都是去看Android源码里有没有人在使用我们所寻找的类似的功能,这无疑拉低了一些效率。除此之外,语言本身作为工具:
1你能强可以自己造轮子。
2等别人造轮子然后拿来开箱即用。
-致敬所有的Android一线研发人员
再注:如果要使用多线程直接调用start_thread_pool就可以让device进行多线程的处理了,其他的hal一般使用多线程处理,用binder提供其他的Bnbinder来实现自己的持久化服务,比如音频的soundtrigger, 和播放录音等。