Skip to main content

android_ble/
l2cap_channel.rs

1// This code is orginally written by <https://github.com/Dirbaio>.
2
3use std::io::{Read, Write};
4use std::sync::Arc;
5use std::task::{Context, Poll};
6use std::{fmt, pin, slice, thread};
7
8use futures_lite::io::{AsyncRead, AsyncWrite, BlockOn};
9use java_spaghetti::{ByteArray, Global, Local, PrimitiveArray};
10use log::{debug, trace, warn};
11
12use super::bindings::android::bluetooth::{BluetoothDevice, BluetoothSocket};
13use super::error::ErrorKind;
14use super::util::OptionExt;
15use super::vm_context::{android_api_level, jni_with_env};
16
17const PIPE_CAPACITY: usize = 0x100000; // 1MB
18
19macro_rules! derive_async_read {
20    ($type:ty, $field:tt) => {
21        impl AsyncRead for $type {
22            fn poll_read(
23                mut self: pin::Pin<&mut Self>,
24                cx: &mut Context<'_>,
25                buf: &mut [u8],
26            ) -> Poll<std::io::Result<usize>> {
27                let reader = pin::pin!(&mut self.$field);
28                reader.poll_read(cx, buf)
29            }
30        }
31    };
32}
33
34macro_rules! derive_async_write {
35    ($type:ty, $field:tt) => {
36        impl AsyncWrite for $type {
37            fn poll_write(
38                mut self: pin::Pin<&mut Self>,
39                cx: &mut Context<'_>,
40                buf: &[u8],
41            ) -> Poll<std::io::Result<usize>> {
42                let writer = pin::pin!(&mut self.$field);
43                writer.poll_write(cx, buf)
44            }
45
46            fn poll_flush(
47                mut self: pin::Pin<&mut Self>,
48                cx: &mut Context<'_>,
49            ) -> Poll<std::io::Result<()>> {
50                let writer = pin::pin!(&mut self.$field);
51                writer.poll_flush(cx)
52            }
53
54            fn poll_close(
55                mut self: pin::Pin<&mut Self>,
56                cx: &mut Context<'_>,
57            ) -> Poll<std::io::Result<()>> {
58                let writer = pin::pin!(&mut self.$field);
59                writer.poll_close(cx)
60            }
61
62            fn poll_write_vectored(
63                mut self: pin::Pin<&mut Self>,
64                cx: &mut Context<'_>,
65                bufs: &[std::io::IoSlice<'_>],
66            ) -> Poll<std::io::Result<usize>> {
67                let writer = pin::pin!(&mut self.$field);
68                writer.poll_write_vectored(cx, bufs)
69            }
70        }
71    };
72}
73
74pub fn open_l2cap_channel(
75    device: Global<BluetoothDevice>,
76    psm: u16,
77    secure: bool,
78) -> std::prelude::v1::Result<(L2capChannelReader, L2capChannelWriter), crate::Error> {
79    if android_api_level() < 29 {
80        return Err(crate::Error::new(
81            ErrorKind::NotSupported,
82            None,
83            "creating L2CAP channel requires Android API level 29 or higher",
84        ));
85    }
86    jni_with_env(|env| {
87        let device = device.as_local(env);
88
89        let channel = if secure {
90            device.createL2capChannel(psm as _)?.non_null()?
91        } else {
92            device.createInsecureL2capChannel(psm as _)?.non_null()?
93        };
94
95        channel.connect()?;
96
97        // The L2capCloser closes the l2cap channel when dropped.
98        // We put it in an Arc held by both the reader and writer, so it gets dropped
99        // when
100        let closer = Arc::new(L2capCloser {
101            channel: channel.as_global(),
102        });
103
104        let (read_receiver, read_sender) = piper::pipe(PIPE_CAPACITY);
105        let (write_receiver, write_sender) = piper::pipe(PIPE_CAPACITY);
106        let input_stream = channel.getInputStream()?.non_null()?.as_global();
107        let output_stream = channel.getOutputStream()?.non_null()?.as_global();
108
109        // Unfortunately, Android's API for L2CAP channels is only blocking. Only way to deal with it
110        // is to launch two background threads with blocking loops for reading and writing, which communicate
111        // with the async Rust world via async channels.
112        //
113        // The loops stop when either Android returns an error (for example if the channel is closed), or the
114        // async channel gets closed because the user dropped the reader or writer structs.
115        thread::spawn(move || {
116            debug!("l2cap read thread running!");
117            let mut read_sender = BlockOn::new(read_sender);
118
119            jni_with_env(|env| {
120                let stream = input_stream.as_local(env);
121                let arr: Local<ByteArray> = ByteArray::new(env, 1024);
122
123                loop {
124                    match stream.read_byte_array(&arr) {
125                        Ok(n) if n < 0 => {
126                            warn!("failed to read from l2cap channel: {}", n);
127                            break;
128                        }
129                        Err(e) => {
130                            warn!("failed to read from l2cap channel: {:?}", e);
131                            break;
132                        }
133                        Ok(n) => {
134                            let n = n as usize;
135                            let mut buf = vec![0u8; n];
136                            arr.get_region(0, u8toi8_mut(&mut buf));
137                            if let Err(e) = read_sender.write_all(&buf) {
138                                warn!("failed to enqueue received l2cap packet: {:?}", e);
139                                break;
140                            }
141                        }
142                    }
143                }
144            });
145
146            debug!("l2cap read thread exiting!");
147        });
148
149        thread::spawn(move || {
150            debug!("l2cap write thread running!");
151            let mut write_receiver = BlockOn::new(write_receiver);
152            jni_with_env(|env| {
153                let stream = output_stream.as_local(env);
154                let mut buf = vec![0; PIPE_CAPACITY];
155
156                loop {
157                    match write_receiver.read(&mut buf) {
158                        Err(e) => {
159                            warn!("failed to dequeue l2cap packet to send: {:?}", e);
160                            break;
161                        }
162                        Ok(0) => {
163                            trace!("Stream ended");
164                            break;
165                        }
166                        Ok(packet) => {
167                            let b = ByteArray::new_from(env, u8toi8(&buf[..packet]));
168                            if let Err(e) = stream.write_byte_array(b) {
169                                warn!("failed to write to l2cap channel: {:?}", e);
170                                break;
171                            };
172                        }
173                    }
174                }
175            });
176
177            debug!("l2cap write thread exiting!");
178        });
179
180        Ok((
181            L2capChannelReader {
182                _closer: closer.clone(),
183                stream: read_receiver,
184            },
185            L2capChannelWriter {
186                _closer: closer,
187                stream: write_sender,
188            },
189        ))
190    })
191}
192
193/// Utility struct to close the channel on drop.
194pub(super) struct L2capCloser {
195    channel: Global<BluetoothSocket>,
196}
197
198impl L2capCloser {
199    fn close(&self) {
200        jni_with_env(|env| {
201            let channel = self.channel.as_local(env);
202            match channel.close() {
203                Ok(()) => debug!("l2cap channel closed"),
204                Err(e) => warn!("failed to close channel: {:?}", e),
205            };
206        });
207    }
208}
209
210impl Drop for L2capCloser {
211    fn drop(&mut self) {
212        self.close()
213    }
214}
215
216/// A Bluetooth LE L2CAP Connection-oriented Channel (CoC).
217pub struct L2capChannel {
218    pub(super) reader: L2capChannelReader,
219    pub(super) writer: L2capChannelWriter,
220}
221
222impl L2capChannel {
223    /// Split the channel into read and write halves.
224    pub fn split(self) -> (L2capChannelReader, L2capChannelWriter) {
225        (self.reader, self.writer)
226    }
227}
228
229derive_async_read!(L2capChannel, reader);
230derive_async_write!(L2capChannel, writer);
231
232/// Reader half of a L2CAP Connection-oriented Channel (CoC).
233pub struct L2capChannelReader {
234    stream: piper::Reader,
235    _closer: Arc<L2capCloser>,
236}
237
238derive_async_read!(L2capChannelReader, stream);
239
240impl fmt::Debug for L2capChannelReader {
241    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
242        f.write_str("L2capChannelReader")
243    }
244}
245
246/// Writer half of a L2CAP Connection-oriented Channel (CoC).
247pub struct L2capChannelWriter {
248    stream: piper::Writer,
249    _closer: Arc<L2capCloser>,
250}
251
252derive_async_write!(L2capChannelWriter, stream);
253
254impl fmt::Debug for L2capChannelWriter {
255    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256        f.write_str("L2capChannelWriter")
257    }
258}
259
260fn u8toi8(slice: &[u8]) -> &[i8] {
261    let len = slice.len();
262    let data = slice.as_ptr() as *const i8;
263    // safety: any bit pattern is valid for u8 and i8, so transmuting them is fine.
264    unsafe { slice::from_raw_parts(data, len) }
265}
266
267fn u8toi8_mut(slice: &mut [u8]) -> &mut [i8] {
268    let len = slice.len();
269    let data = slice.as_mut_ptr() as *mut i8;
270    // safety: any bit pattern is valid for u8 and i8, so transmuting them is fine.
271    unsafe { slice::from_raw_parts_mut(data, len) }
272}