btleplug 0.13.0

A Cross-Platform Rust Bluetooth Low Energy (BLE) GATT library.
Documentation
pub mod arrays;
pub mod exceptions;
pub mod future;
pub mod ops;
pub mod stream;
pub mod task;
pub mod uuid;

#[cfg(test)]
pub(crate) mod test_utils {
    use jni::{
        Env, JavaVM, NativeMethod, jni_sig, jni_str,
        objects::{Global, JObject, Reference},
    };
    use lazy_static::lazy_static;
    use std::{
        cell::Cell,
        ffi::c_void,
        sync::{Arc, Mutex},
        task::{Wake, Waker},
    };

    fn test_init(env: &mut Env) -> jni::errors::Result<()> {
        use super::{
            future::{JFuture, JFutureException},
            ops::{JFnAdapter, JFnBiFunctionImpl, JFnFunctionImpl, JFnRunnableImpl},
            stream::{JStream, JStreamPoll},
            task::{JPollResult, JWaker},
        };

        let loader = jni::objects::LoaderContext::default();
        <JFuture as Reference>::lookup_class(env, &loader)?;
        <JFutureException as Reference>::lookup_class(env, &loader)?;
        <JFnAdapter as Reference>::lookup_class(env, &loader)?;
        <JStream as Reference>::lookup_class(env, &loader)?;
        <JStreamPoll as Reference>::lookup_class(env, &loader)?;
        <JWaker as Reference>::lookup_class(env, &loader)?;
        <JPollResult as Reference>::lookup_class(env, &loader)?;
        <JFnRunnableImpl as Reference>::lookup_class(env, &loader)?;
        <JFnBiFunctionImpl as Reference>::lookup_class(env, &loader)?;
        <JFnFunctionImpl as Reference>::lookup_class(env, &loader)?;

        let fn_adapter_class = <JFnAdapter as Reference>::lookup_class(env, &loader)?;
        unsafe {
            env.register_native_methods(
            &*fn_adapter_class,
            &[
                NativeMethod::from_raw_parts(
                    jni_str!("callInternal"),
                    jni_str!("(Ljava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;"),
                    super::ops::fn_adapter_call_internal as *mut c_void,
                ),
                NativeMethod::from_raw_parts(
                    jni_str!("closeInternal"),
                    jni_str!("()V"),
                    super::ops::fn_adapter_close_internal as *mut c_void,
                ),
            ],
        )?
        };
        Ok(())
    }

    pub struct TestWakerData(Mutex<bool>);

    impl TestWakerData {
        pub fn new() -> Self {
            Self(Mutex::new(false))
        }

        pub fn value(&self) -> bool {
            *self.0.lock().unwrap()
        }

        pub fn set_value(&self, value: bool) {
            let mut guard = self.0.lock().unwrap();
            *guard = value;
        }
    }

    impl Wake for TestWakerData {
        fn wake(self: Arc<Self>) {
            Self::wake_by_ref(&self);
        }

        fn wake_by_ref(self: &Arc<Self>) {
            self.set_value(true);
        }
    }

    pub fn test_waker(data: &Arc<TestWakerData>) -> Waker {
        Waker::from(data.clone())
    }

    struct GlobalJVM {
        jvm: JavaVM,
        class_loader: Global<JObject<'static>>,
    }

    thread_local! {
        static CLASS_LOADER_SET: Cell<bool> = const { Cell::new(false) };
    }

    pub fn with_env<F, T>(f: F) -> jni::errors::Result<T>
    where
        F: FnOnce(&mut Env) -> jni::errors::Result<T>,
    {
        JVM.jvm.attach_current_thread(|env| {
            if !CLASS_LOADER_SET.with(|c| c.get()) {
                let thread = env
                    .call_static_method(
                        jni_str!("java/lang/Thread"),
                        jni_str!("currentThread"),
                        jni_sig!("()Ljava/lang/Thread;"),
                        &[],
                    )?
                    .l()?;
                env.call_method(
                    &thread,
                    jni_str!("setContextClassLoader"),
                    jni_sig!("(Ljava/lang/ClassLoader;)V"),
                    &[JVM.class_loader.as_obj().into()],
                )?;
                CLASS_LOADER_SET.with(|c| c.set(true));
            }
            f(env)
        })
    }

    lazy_static! {
        static ref JVM: GlobalJVM = {
            use jni::InitArgsBuilder;
            use std::{env, path::PathBuf};

            let mut jni_utils_jar = PathBuf::from(env::current_exe().unwrap());
            jni_utils_jar.pop();
            jni_utils_jar.pop();
            jni_utils_jar.push("java");
            jni_utils_jar.push("libs");
            jni_utils_jar.push("btleplug-jni.jar");

            let classpath = format!("-Djava.class.path={}", jni_utils_jar.to_str().unwrap());
            let jvm_args = InitArgsBuilder::new().option(&classpath).build().unwrap();
            let jvm = JavaVM::new(jvm_args).unwrap();

            let class_loader = jvm
                .attach_current_thread(|env| {
                    test_init(env).unwrap();

                    let thread = env
                        .call_static_method(
                            jni_str!("java/lang/Thread"),
                            jni_str!("currentThread"),
                            jni_sig!("()Ljava/lang/Thread;"),
                            &[],
                        )
                        .unwrap()
                        .l()
                        .unwrap();
                    let class_loader = env
                        .call_method(
                            &thread,
                            jni_str!("getContextClassLoader"),
                            jni_sig!("()Ljava/lang/ClassLoader;"),
                            &[],
                        )
                        .unwrap()
                        .l()
                        .unwrap();
                    Ok::<_, jni::errors::Error>(env.new_global_ref(class_loader).unwrap())
                })
                .unwrap();

            GlobalJVM { jvm, class_loader }
        };
    }

    #[test]
    fn with_env_is_safe_across_threads() {
        use std::sync::{Arc, Barrier, mpsc};

        let barrier = Arc::new(Barrier::new(2));
        let worker_barrier = barrier.clone();
        let (tx, rx) = mpsc::channel();
        let worker = std::thread::spawn(move || {
            worker_barrier.wait();
            with_env(|env| {
                let thread = env
                    .call_static_method(
                        jni_str!("java/lang/Thread"),
                        jni_str!("currentThread"),
                        jni_sig!("()Ljava/lang/Thread;"),
                        &[],
                    )?
                    .l()?;
                let name = env
                    .call_method(
                        &thread,
                        jni_str!("getName"),
                        jni_sig!("()Ljava/lang/String;"),
                        &[],
                    )?
                    .l()?;
                let name = env.cast_local::<jni::objects::JString>(name)?.to_string();
                tx.send(name).unwrap();
                Ok(())
            })
            .unwrap();
        });

        barrier.wait();
        worker.join().unwrap();
        assert!(!rx.recv().unwrap().is_empty());
    }
}