Skip to content

Commit 5172e94

Browse files
committed
android: add l2cap connection-oriented channel support.
part of #3
1 parent 569aed8 commit 5172e94

16 files changed

+505
-1
lines changed

src/android/adapter.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ fn on_scan_result(env: Env<'_>, id: i32, callback_type: i32, scan_result: Arg<Sc
288288
let device_id = DeviceId(address);
289289

290290
let d = AdvertisingDevice {
291-
device: Device(DeviceImpl { id: device_id }),
291+
device: Device(DeviceImpl {
292+
id: device_id,
293+
device: device.as_global(),
294+
}),
292295
adv_data: AdvertisementData {
293296
is_connectable,
294297
local_name,

src/android/device.rs

+12
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
use futures_core::Stream;
22
use futures_lite::stream;
3+
use java_spaghetti::Global;
34
use uuid::Uuid;
45

6+
use super::bindings::android::bluetooth::BluetoothDevice;
7+
use super::l2cap_channel::{L2capChannelReader, L2capChannelWriter};
58
use crate::pairing::PairingAgent;
69
use crate::{DeviceId, Result, Service, ServicesChanged};
710

811
#[derive(Clone)]
912
pub struct DeviceImpl {
1013
pub(super) id: DeviceId,
14+
pub(super) device: Global<BluetoothDevice>,
1115
}
1216

1317
impl PartialEq for DeviceImpl {
@@ -92,6 +96,14 @@ impl DeviceImpl {
9296
pub async fn rssi(&self) -> Result<i16> {
9397
todo!()
9498
}
99+
100+
pub async fn open_l2cap_channel(
101+
&self,
102+
psm: u16,
103+
secure: bool,
104+
) -> std::prelude::v1::Result<(L2capChannelReader, L2capChannelWriter), crate::Error> {
105+
super::l2cap_channel::open_l2cap_channel(self.device.clone(), psm, secure)
106+
}
95107
}
96108

97109
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

src/android/l2cap_channel.rs

+218
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
use std::sync::Arc;
2+
use std::{fmt, slice, thread};
3+
4+
use async_channel::{Receiver, Sender};
5+
use java_spaghetti::{ByteArray, Global, Local, PrimitiveArray};
6+
use tracing::{debug, warn};
7+
8+
use super::bindings::android::bluetooth::{BluetoothDevice, BluetoothSocket};
9+
use super::OptionExt;
10+
use crate::error::ErrorKind;
11+
use crate::{Error, Result};
12+
13+
pub fn open_l2cap_channel(
14+
device: Global<BluetoothDevice>,
15+
psm: u16,
16+
secure: bool,
17+
) -> std::prelude::v1::Result<(L2capChannelReader, L2capChannelWriter), crate::Error> {
18+
device.vm().with_env(|env| {
19+
let device = device.as_local(env);
20+
21+
let channel = if secure {
22+
device.createL2capChannel(psm as _)?.non_null()?
23+
} else {
24+
device.createInsecureL2capChannel(psm as _)?.non_null()?
25+
};
26+
27+
channel.connect()?;
28+
29+
// The L2capCloser closes the l2cap channel when dropped.
30+
// We put it in an Arc held by both the reader and writer, so it gets dropped
31+
// when
32+
let closer = Arc::new(L2capCloser {
33+
channel: channel.as_global(),
34+
});
35+
36+
let (read_sender, read_receiver) = async_channel::bounded::<Vec<u8>>(16);
37+
let (write_sender, write_receiver) = async_channel::bounded::<Vec<u8>>(16);
38+
let input_stream = channel.getInputStream()?.non_null()?.as_global();
39+
let output_stream = channel.getOutputStream()?.non_null()?.as_global();
40+
41+
// Unfortunately, Android's API for L2CAP channels is only blocking. Only way to deal with it
42+
// is to launch two background threads with blocking loops for reading and writing, which communicate
43+
// with the async Rust world via async channels.
44+
//
45+
// The loops stop when either Android returns an error (for example if the channel is closed), or the
46+
// async channel gets closed because the user dropped the reader or writer structs.
47+
thread::spawn(move || {
48+
debug!("l2cap read thread running!");
49+
50+
input_stream.vm().with_env(|env| {
51+
let stream = input_stream.as_local(env);
52+
let arr: Local<ByteArray> = ByteArray::new(env, 1024);
53+
54+
loop {
55+
match stream.read_byte_array(&*arr) {
56+
Ok(n) if n < 0 => {
57+
warn!("failed to read from l2cap channel: {}", n);
58+
break;
59+
}
60+
Err(e) => {
61+
warn!("failed to read from l2cap channel: {:?}", e);
62+
break;
63+
}
64+
Ok(n) => {
65+
let n = n as usize;
66+
let mut buf = vec![0u8; n];
67+
arr.get_region(0, u8toi8_mut(&mut buf));
68+
if let Err(e) = read_sender.send_blocking(buf) {
69+
warn!("failed to enqueue received l2cap packet: {:?}", e);
70+
break;
71+
}
72+
}
73+
}
74+
}
75+
});
76+
77+
debug!("l2cap read thread exiting!");
78+
});
79+
80+
thread::spawn(move || {
81+
debug!("l2cap write thread running!");
82+
83+
output_stream.vm().with_env(|env| {
84+
let stream = output_stream.as_local(env);
85+
86+
loop {
87+
match write_receiver.recv_blocking() {
88+
Err(e) => {
89+
warn!("failed to dequeue l2cap packet to send: {:?}", e);
90+
break;
91+
}
92+
Ok(packet) => {
93+
let b = PrimitiveArray::from(env, u8toi8(&packet));
94+
if let Err(e) = stream.write_byte_array(Some(&*b)) {
95+
warn!("failed to write to l2cap channel: {:?}", e);
96+
break;
97+
};
98+
}
99+
}
100+
}
101+
});
102+
103+
debug!("l2cap write thread exiting!");
104+
});
105+
106+
Ok((
107+
L2capChannelReader {
108+
closer: closer.clone(),
109+
stream: read_receiver,
110+
},
111+
L2capChannelWriter {
112+
closer,
113+
stream: write_sender,
114+
},
115+
))
116+
})
117+
}
118+
119+
/// Utility struct to close the channel on drop.
120+
pub(super) struct L2capCloser {
121+
channel: Global<BluetoothSocket>,
122+
}
123+
124+
impl L2capCloser {
125+
fn close(&self) {
126+
self.channel.vm().with_env(|env| {
127+
let channel = self.channel.as_local(env);
128+
match channel.close() {
129+
Ok(()) => debug!("l2cap channel closed"),
130+
Err(e) => warn!("failed to close channel: {:?}", e),
131+
};
132+
});
133+
}
134+
}
135+
136+
impl Drop for L2capCloser {
137+
fn drop(&mut self) {
138+
self.close()
139+
}
140+
}
141+
142+
pub struct L2capChannelReader {
143+
stream: Receiver<Vec<u8>>,
144+
closer: Arc<L2capCloser>,
145+
}
146+
147+
impl L2capChannelReader {
148+
#[inline]
149+
pub async fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
150+
let packet = self
151+
.stream
152+
.recv()
153+
.await
154+
.map_err(|_| Error::new(ErrorKind::ConnectionFailed, None, "L2CAP channel is closed".to_string()))?;
155+
156+
if packet.len() > buf.len() {
157+
return Err(Error::new(
158+
ErrorKind::InvalidParameter,
159+
None,
160+
"Buffer is too small".to_string(),
161+
));
162+
}
163+
164+
buf[..packet.len()].copy_from_slice(&packet);
165+
166+
Ok(packet.len())
167+
}
168+
169+
pub async fn close(&mut self) -> Result<()> {
170+
self.closer.close();
171+
Ok(())
172+
}
173+
}
174+
175+
impl fmt::Debug for L2capChannelReader {
176+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177+
f.write_str("L2capChannelReader")
178+
}
179+
}
180+
181+
pub struct L2capChannelWriter {
182+
stream: Sender<Vec<u8>>,
183+
closer: Arc<L2capCloser>,
184+
}
185+
186+
impl L2capChannelWriter {
187+
pub async fn write(&mut self, packet: &[u8]) -> Result<()> {
188+
self.stream
189+
.send(packet.to_vec())
190+
.await
191+
.map_err(|_| Error::new(ErrorKind::ConnectionFailed, None, "L2CAP channel is closed".to_string()))
192+
}
193+
194+
pub async fn close(&mut self) -> Result<()> {
195+
self.closer.close();
196+
Ok(())
197+
}
198+
}
199+
200+
impl fmt::Debug for L2capChannelWriter {
201+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
202+
f.write_str("L2capChannelWriter")
203+
}
204+
}
205+
206+
fn u8toi8(slice: &[u8]) -> &[i8] {
207+
let len = slice.len();
208+
let data = slice.as_ptr() as *const i8;
209+
// safety: any bit pattern is valid for u8 and i8, so transmuting them is fine.
210+
unsafe { slice::from_raw_parts(data, len) }
211+
}
212+
213+
fn u8toi8_mut(slice: &mut [u8]) -> &mut [i8] {
214+
let len = slice.len();
215+
let data = slice.as_mut_ptr() as *mut i8;
216+
// safety: any bit pattern is valid for u8 and i8, so transmuting them is fine.
217+
unsafe { slice::from_raw_parts_mut(data, len) }
218+
}

src/android/mod.rs

+2
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,13 @@ pub mod adapter;
77
pub mod characteristic;
88
pub mod descriptor;
99
pub mod device;
10+
pub mod l2cap_channel;
1011
pub mod service;
1112

1213
pub(crate) mod bindings;
1314

1415
/// A platform-specific device identifier.
16+
/// On android it contains the Bluetooth address in the format `AB:CD:EF:01:23:45`.
1517
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
1618
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
1719
pub struct DeviceId(pub(crate) String);

src/bluer.rs

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ pub mod adapter;
22
pub mod characteristic;
33
pub mod descriptor;
44
pub mod device;
5+
pub mod l2cap_channel;
56
pub mod service;
67

78
mod error;

src/bluer/device.rs

+9
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::sync::Arc;
33
use futures_core::Stream;
44
use futures_lite::StreamExt;
55

6+
use super::l2cap_channel::{L2capChannelReader, L2capChannelWriter};
67
use super::DeviceId;
78
use crate::device::ServicesChanged;
89
use crate::error::ErrorKind;
@@ -290,6 +291,14 @@ impl DeviceImpl {
290291
is_connectable,
291292
}
292293
}
294+
295+
pub async fn open_l2cap_channel(
296+
&self,
297+
_psm: u16,
298+
_secure: bool,
299+
) -> std::prelude::v1::Result<(L2capChannelReader, L2capChannelWriter), crate::Error> {
300+
Err(ErrorKind::NotSupported.into())
301+
}
293302
}
294303

295304
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

src/bluer/l2cap_channel.rs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
use std::fmt;
2+
3+
use crate::Result;
4+
5+
pub struct L2capChannelReader {
6+
_private: (),
7+
}
8+
9+
impl L2capChannelReader {
10+
#[inline]
11+
pub async fn read(&mut self, _buf: &mut [u8]) -> Result<usize> {
12+
todo!()
13+
}
14+
15+
pub async fn close(&mut self) -> Result<()> {
16+
todo!()
17+
}
18+
}
19+
20+
impl fmt::Debug for L2capChannelReader {
21+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22+
f.write_str("L2capChannelReader")
23+
}
24+
}
25+
26+
pub struct L2capChannelWriter {
27+
_private: (),
28+
}
29+
30+
impl L2capChannelWriter {
31+
pub async fn write(&mut self, _packet: &[u8]) -> Result<()> {
32+
todo!()
33+
}
34+
35+
pub async fn close(&mut self) -> Result<()> {
36+
todo!()
37+
}
38+
}
39+
40+
impl fmt::Debug for L2capChannelWriter {
41+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42+
f.write_str("L2capChannelWriter")
43+
}
44+
}

src/corebluetooth.rs

+1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub mod characteristic;
55
pub mod descriptor;
66
pub mod device;
77
pub mod error;
8+
pub mod l2cap_channel;
89
pub mod service;
910

1011
mod delegates;

src/corebluetooth/device.rs

+9
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use objc_foundation::{INSArray, INSFastEnumeration, INSString, NSArray};
66
use objc_id::ShareId;
77

88
use super::delegates::{PeripheralDelegate, PeripheralEvent};
9+
use super::l2cap_channel::{L2capChannelReader, L2capChannelWriter};
910
use super::types::{CBPeripheral, CBPeripheralState, CBService, CBUUID};
1011
use crate::device::ServicesChanged;
1112
use crate::error::ErrorKind;
@@ -213,6 +214,14 @@ impl DeviceImpl {
213214
}
214215
}
215216
}
217+
218+
pub async fn open_l2cap_channel(
219+
&self,
220+
_psm: u16,
221+
_secure: bool,
222+
) -> std::prelude::v1::Result<(L2capChannelReader, L2capChannelWriter), crate::Error> {
223+
Err(ErrorKind::NotSupported.into())
224+
}
216225
}
217226

218227
#[derive(Debug, Clone, PartialEq, Eq, Hash)]

0 commit comments

Comments
 (0)