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