1 // Copyright 2023 Google LLC
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14
15 /// L2CAP CoC server bridge: waits for a peer to connect an L2CAP CoC channel
16 /// on a specified PSM. When the connection is made, the bridge connects a TCP
17 /// socket to a remote host and bridges the data in both directions, with flow
18 /// control.
19 /// When the L2CAP CoC channel is closed, the bridge disconnects the TCP socket
20 /// and waits for a new L2CAP CoC channel to be connected.
21 /// When the TCP connection is closed by the TCP server, the L2CAP connection is closed as well.
22 use crate::cli::l2cap::{proxy_l2cap_rx_to_tcp_tx, proxy_tcp_rx_to_l2cap_tx, BridgeData};
23 use bumble::wrapper::{device::Device, hci::HciConstant, l2cap::LeConnectionOrientedChannel};
24 use futures::executor::block_on;
25 use owo_colors::OwoColorize;
26 use pyo3::{PyResult, Python};
27 use std::{sync::Arc, time::Duration};
28 use tokio::{
29 join,
30 net::TcpStream,
31 select,
32 sync::{mpsc, Mutex},
33 };
34
35 pub struct Args {
36 pub psm: u16,
37 pub max_credits: Option<u16>,
38 pub mtu: Option<u16>,
39 pub mps: Option<u16>,
40 pub tcp_host: String,
41 pub tcp_port: u16,
42 }
43
start(args: &Args, device: &mut Device) -> PyResult<()>44 pub async fn start(args: &Args, device: &mut Device) -> PyResult<()> {
45 let host = args.tcp_host.clone();
46 let port = args.tcp_port;
47 device.register_l2cap_channel_server(
48 args.psm,
49 move |py, l2cap_channel| {
50 let channel_info = l2cap_channel
51 .debug_string()
52 .unwrap_or_else(|e| format!("failed to get l2cap channel info ({e})"));
53 println!("{} {channel_info}", "*** L2CAP channel:".cyan());
54
55 let host = host.clone();
56 // Handles setting up a tokio runtime that runs this future to completion while also
57 // containing the necessary context vars.
58 pyo3_asyncio::tokio::future_into_py(
59 py,
60 proxy_data_between_l2cap_and_tcp(l2cap_channel, host, port),
61 )?;
62 Ok(())
63 },
64 args.max_credits,
65 args.mtu,
66 args.mps,
67 )?;
68
69 println!(
70 "{}",
71 format!("### Listening for CoC connection on PSM {}", args.psm).yellow()
72 );
73
74 device.on_connection(|_py, mut connection| {
75 let connection_info = connection
76 .debug_string()
77 .unwrap_or_else(|e| format!("failed to get connection info ({e})"));
78 println!(
79 "{} {}",
80 "@@@ Bluetooth connection: ".green(),
81 connection_info,
82 );
83 connection.on_disconnection(|_py, reason| {
84 let disconnection_info = match HciConstant::error_name(reason) {
85 Ok(info_string) => info_string,
86 Err(py_err) => format!("failed to get disconnection error name ({})", py_err),
87 };
88 println!(
89 "{} {}",
90 "@@@ Bluetooth disconnection: ".red(),
91 disconnection_info,
92 );
93 Ok(())
94 })?;
95 Ok(())
96 })?;
97
98 device.start_advertising(false).await?;
99
100 Ok(())
101 }
102
proxy_data_between_l2cap_and_tcp( mut l2cap_channel: LeConnectionOrientedChannel, tcp_host: String, tcp_port: u16, ) -> PyResult<()>103 async fn proxy_data_between_l2cap_and_tcp(
104 mut l2cap_channel: LeConnectionOrientedChannel,
105 tcp_host: String,
106 tcp_port: u16,
107 ) -> PyResult<()> {
108 let (l2cap_to_tcp_tx, mut l2cap_to_tcp_rx) = mpsc::channel::<BridgeData>(10);
109
110 // Set callback (`set_sink`) for when l2cap data is received.
111 let l2cap_to_tcp_tx_clone = l2cap_to_tcp_tx.clone();
112 l2cap_channel
113 .set_sink(move |_py, sdu| {
114 block_on(l2cap_to_tcp_tx_clone.send(BridgeData::Data(sdu.into())))
115 .expect("failed to channel data to tcp");
116 Ok(())
117 })
118 .expect("failed to set sink for l2cap connection");
119
120 // Set l2cap callback for when the channel is closed.
121 l2cap_channel
122 .on_close(move |_py| {
123 println!("{}", "*** L2CAP channel closed".red());
124 block_on(l2cap_to_tcp_tx.send(BridgeData::CloseSignal))
125 .expect("failed to channel close signal to tcp");
126 Ok(())
127 })
128 .expect("failed to set on_close callback for l2cap channel");
129
130 println!(
131 "{}",
132 format!("### Connecting to TCP {tcp_host}:{tcp_port}...").yellow()
133 );
134
135 let l2cap_channel = Arc::new(Mutex::new(Some(l2cap_channel)));
136 let tcp_stream = match TcpStream::connect(format!("{tcp_host}:{tcp_port}")).await {
137 Ok(stream) => {
138 println!("{}", "### Connected".green());
139 Some(stream)
140 }
141 Err(err) => {
142 println!("{}", format!("!!! Connection failed: {err}").red());
143 if let Some(mut channel) = l2cap_channel.lock().await.take() {
144 // Bumble might enter an invalid state if disconnection request is received from
145 // l2cap client before receiving a disconnection response from the same client,
146 // blocking this async call from returning.
147 // See: https://github.com/google/bumble/issues/257
148 select! {
149 res = channel.disconnect() => {
150 let _ = res.map_err(|e| eprintln!("Failed to call disconnect on l2cap channel: {e}"));
151 },
152 _ = tokio::time::sleep(Duration::from_secs(1)) => eprintln!("Timed out while calling disconnect on l2cap channel."),
153 }
154 }
155 None
156 }
157 };
158
159 match tcp_stream {
160 None => {
161 while let Some(bridge_data) = l2cap_to_tcp_rx.recv().await {
162 match bridge_data {
163 BridgeData::Data(sdu) => {
164 println!("{}", format!("<<< [L2CAP SDU]: {} bytes", sdu.len()).cyan());
165 println!("{}", "!!! TCP socket not open, dropping".red())
166 }
167 BridgeData::CloseSignal => break,
168 }
169 }
170 }
171 Some(tcp_stream) => {
172 let (tcp_reader, tcp_writer) = tcp_stream.into_split();
173
174 // Do tcp stuff when something happens on the l2cap channel.
175 let handle_l2cap_data_future =
176 proxy_l2cap_rx_to_tcp_tx(l2cap_to_tcp_rx, tcp_writer, l2cap_channel.clone());
177
178 // Do l2cap stuff when something happens on tcp.
179 let handle_tcp_data_future =
180 proxy_tcp_rx_to_l2cap_tx(tcp_reader, l2cap_channel.clone(), false);
181
182 let (handle_l2cap_result, handle_tcp_result) =
183 join!(handle_l2cap_data_future, handle_tcp_data_future);
184
185 if let Err(e) = handle_l2cap_result {
186 println!("!!! Error: {e}");
187 }
188
189 if let Err(e) = handle_tcp_result {
190 println!("!!! Error: {e}");
191 }
192 }
193 };
194
195 Python::with_gil(|_| {
196 // Must hold GIL at least once while/after dropping for Python heap object to ensure
197 // de-allocation.
198 drop(l2cap_channel);
199 });
200
201 Ok(())
202 }
203