Skip to main content

fuchsia_bluetooth/types/
channel.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use fidl::endpoints::{ClientEnd, Proxy};
6use fidl_fuchsia_bluetooth as fidl_bt;
7use fidl_fuchsia_bluetooth_bredr as bredr;
8use fuchsia_sync::Mutex;
9use futures::sink::Sink;
10use futures::stream::{FusedStream, Stream};
11use futures::{Future, StreamExt};
12use log::warn;
13use std::fmt;
14use std::pin::Pin;
15use std::sync::Arc;
16use std::task::{Context, Poll};
17
18use crate::error::Error;
19
20pub mod socket;
21
22// TODO(b/414410187): Add mod for FIDL client/server.
23
24use socket::SocketConnection;
25
26/// The Channel mode in use for a L2CAP channel.
27#[derive(PartialEq, Debug, Clone)]
28pub enum ChannelMode {
29    Basic,
30    EnhancedRetransmissionMode,
31    LeCreditBasedFlowControl,
32    EnhancedCreditBasedFlowControl,
33}
34
35impl fmt::Display for ChannelMode {
36    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
37        match self {
38            ChannelMode::Basic => write!(f, "Basic"),
39            ChannelMode::EnhancedRetransmissionMode => write!(f, "ERTM"),
40            ChannelMode::LeCreditBasedFlowControl => write!(f, "LE_Credit"),
41            ChannelMode::EnhancedCreditBasedFlowControl => write!(f, "Credit"),
42        }
43    }
44}
45
46pub enum A2dpDirection {
47    Normal,
48    Source,
49    Sink,
50}
51
52impl From<A2dpDirection> for bredr::A2dpDirectionPriority {
53    fn from(pri: A2dpDirection) -> Self {
54        match pri {
55            A2dpDirection::Normal => bredr::A2dpDirectionPriority::Normal,
56            A2dpDirection::Source => bredr::A2dpDirectionPriority::Source,
57            A2dpDirection::Sink => bredr::A2dpDirectionPriority::Sink,
58        }
59    }
60}
61
62impl TryFrom<fidl_bt::ChannelMode> for ChannelMode {
63    type Error = Error;
64    fn try_from(fidl: fidl_bt::ChannelMode) -> Result<Self, Error> {
65        match fidl {
66            fidl_bt::ChannelMode::Basic => Ok(ChannelMode::Basic),
67            fidl_bt::ChannelMode::EnhancedRetransmission => {
68                Ok(ChannelMode::EnhancedRetransmissionMode)
69            }
70            fidl_bt::ChannelMode::LeCreditBasedFlowControl => {
71                Ok(ChannelMode::LeCreditBasedFlowControl)
72            }
73            fidl_bt::ChannelMode::EnhancedCreditBasedFlowControl => {
74                Ok(ChannelMode::EnhancedCreditBasedFlowControl)
75            }
76            x => Err(Error::FailedConversion(format!("Unsupported channel mode type: {x:?}"))),
77        }
78    }
79}
80
81impl From<ChannelMode> for fidl_bt::ChannelMode {
82    fn from(x: ChannelMode) -> Self {
83        match x {
84            ChannelMode::Basic => fidl_bt::ChannelMode::Basic,
85            ChannelMode::EnhancedRetransmissionMode => fidl_bt::ChannelMode::EnhancedRetransmission,
86            ChannelMode::LeCreditBasedFlowControl => fidl_bt::ChannelMode::LeCreditBasedFlowControl,
87            ChannelMode::EnhancedCreditBasedFlowControl => {
88                fidl_bt::ChannelMode::EnhancedCreditBasedFlowControl
89            }
90        }
91    }
92}
93
94pub enum ConnectionBackendType {
95    Socket,
96    FidlClient,
97    FidlServer,
98}
99
100/// A trait representing a Bluetooth data connection.
101/// Concrete implementations handle the specific transport mechanism (e.g., socket or FIDL protocol)
102/// while fulfilling the `Sink` and `Stream` contracts for data transfer.
103pub trait Connection:
104    Stream<Item = Result<Vec<u8>, zx::Status>>
105    + Sink<Vec<u8>, Error = zx::Status>
106    + Send
107    + Sync
108    + std::fmt::Debug
109    + Unpin
110{
111    /// Returns a future that resolves when the connection is closed.
112    fn closed<'a>(&'a self) -> Pin<Box<dyn Future<Output = Result<(), zx::Status>> + 'a>>;
113
114    /// Returns the type of the connection backend.
115    fn connection_type(&self) -> ConnectionBackendType;
116
117    /// Writes data to the connection. This is a non-blocking fast path.
118    /// Returns `SHOULD_WAIT` if the buffer is full.
119    fn write(&self, bytes: &[u8]) -> Result<usize, zx::Status>;
120
121    /// Returns true if the connection is currently closed.
122    fn is_closed(&self) -> bool;
123
124    /// Consumes the connection and returns a partially filled FIDL channel
125    /// containing the transport (e.g., socket handle) if applicable.
126    fn into_fidl_channel(self: Box<Self>) -> Result<bredr::Channel, zx::Status>;
127}
128
129/// A wrapper for Bluetooth channel. Profiles interact with this struct.
130#[derive(Debug)]
131pub struct Channel {
132    pub(crate) connection: Box<dyn Connection>,
133    mode: ChannelMode,
134    max_tx_size: usize,
135    flush_timeout: Arc<Mutex<Option<zx::MonotonicDuration>>>,
136    audio_direction_ext: Option<bredr::AudioDirectionExtProxy>,
137    l2cap_parameters_ext: Option<bredr::L2capParametersExtProxy>,
138    audio_offload_ext: Option<bredr::AudioOffloadExtProxy>,
139    terminated: bool,
140}
141
142impl Channel {
143    pub const DEFAULT_MAX_TX: usize = 672;
144
145    pub fn from_socket(socket: zx::Socket, max_tx_size: usize) -> Result<Self, zx::Status> {
146        let connection = Box::new(SocketConnection::new(socket));
147        Ok(Channel {
148            connection,
149            mode: ChannelMode::Basic,
150            max_tx_size,
151            flush_timeout: Arc::new(Mutex::new(None)),
152            audio_direction_ext: None,
153            l2cap_parameters_ext: None,
154            audio_offload_ext: None,
155            terminated: false,
156        })
157    }
158
159    pub fn from_socket_infallible(socket: zx::Socket, max_tx_size: usize) -> Self {
160        Self::from_socket(socket, max_tx_size).unwrap()
161    }
162
163    pub fn create() -> (Self, Self) {
164        Self::create_with_max_tx(Self::DEFAULT_MAX_TX)
165    }
166
167    pub fn create_with_max_tx(max_tx_size: usize) -> (Self, Self) {
168        let (remote, local) = zx::Socket::create_datagram();
169        (
170            Channel::from_socket(remote, max_tx_size).unwrap(),
171            Channel::from_socket(local, max_tx_size).unwrap(),
172        )
173    }
174
175    pub fn max_tx_size(&self) -> usize {
176        self.max_tx_size
177    }
178
179    pub fn channel_mode(&self) -> &ChannelMode {
180        &self.mode
181    }
182
183    pub fn flush_timeout(&self) -> Option<zx::MonotonicDuration> {
184        self.flush_timeout.lock().clone()
185    }
186
187    pub fn closed<'a>(&'a self) -> impl Future<Output = Result<(), zx::Status>> + 'a {
188        self.connection.closed()
189    }
190
191    pub fn is_closed(&self) -> bool {
192        self.connection.is_closed()
193    }
194
195    pub fn write(&self, bytes: &[u8]) -> Result<usize, zx::Status> {
196        self.connection.write(bytes)
197    }
198
199    pub fn set_audio_priority(
200        &self,
201        dir: A2dpDirection,
202    ) -> impl Future<Output = Result<(), Error>> + use<> {
203        let proxy = self.audio_direction_ext.clone();
204        async move {
205            match proxy {
206                None => return Err(Error::profile("audio priority not supported")),
207                Some(proxy) => proxy
208                    .set_priority(dir.into())
209                    .await?
210                    .map_err(|e| Error::profile(format!("setting priority failed: {e:?}"))),
211            }
212        }
213    }
214
215    pub fn set_flush_timeout(
216        &self,
217        duration: Option<zx::MonotonicDuration>,
218    ) -> impl Future<Output = Result<Option<zx::MonotonicDuration>, Error>> + use<> {
219        let flush_timeout = self.flush_timeout.clone();
220        let current = self.flush_timeout.lock().clone();
221        let proxy = self.l2cap_parameters_ext.clone();
222        async move {
223            match (current, duration) {
224                (None, None) => return Ok(None),
225                (Some(old), Some(new)) if (old - new).into_millis().abs() < 2 => {
226                    return Ok(current);
227                }
228                _ => {}
229            };
230            let proxy =
231                proxy.ok_or_else(|| Error::profile("l2cap parameter changing not supported"))?;
232            let parameters = fidl_bt::ChannelParameters {
233                flush_timeout: duration.clone().map(zx::MonotonicDuration::into_nanos),
234                ..Default::default()
235            };
236            let new_params = proxy.request_parameters(&parameters).await?;
237            let new_timeout = new_params.flush_timeout.map(zx::MonotonicDuration::from_nanos);
238            *(flush_timeout.lock()) = new_timeout.clone();
239            Ok(new_timeout)
240        }
241    }
242
243    pub fn audio_offload(&self) -> Option<bredr::AudioOffloadExtProxy> {
244        self.audio_offload_ext.clone()
245    }
246}
247
248impl Stream for Channel {
249    type Item = Result<Vec<u8>, zx::Status>;
250
251    fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
252        let this = self.get_mut();
253        if this.terminated {
254            warn!("Stream was polled after termination");
255            return Poll::Ready(None);
256        }
257        let res = this.connection.poll_next_unpin(cx);
258        if let Poll::Ready(None) = res {
259            this.terminated = true;
260        }
261        res
262    }
263}
264
265impl FusedStream for Channel {
266    fn is_terminated(&self) -> bool {
267        self.terminated
268    }
269}
270
271impl Sink<Vec<u8>> for Channel {
272    type Error = zx::Status;
273
274    fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
275        Pin::new(&mut *self.get_mut().connection).poll_ready(cx)
276    }
277
278    fn start_send(self: Pin<&mut Self>, item: Vec<u8>) -> Result<(), Self::Error> {
279        Pin::new(&mut *self.get_mut().connection).start_send(item)
280    }
281
282    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
283        Pin::new(&mut *self.get_mut().connection).poll_flush(cx)
284    }
285
286    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
287        Pin::new(&mut *self.get_mut().connection).poll_close(cx)
288    }
289}
290
291impl TryFrom<Channel> for bredr::Channel {
292    type Error = Error;
293
294    fn try_from(channel: Channel) -> Result<Self, Self::Error> {
295        let mut fidl_channel = channel
296            .connection
297            .into_fidl_channel()
298            .map_err(|e| Error::profile(format!("Failed to convert to FIDL channel: {e:?}")))?;
299
300        fidl_channel.channel_mode = Some(channel.mode.into());
301        fidl_channel.max_tx_sdu_size = Some(channel.max_tx_size as u16);
302
303        let flush_timeout = channel.flush_timeout.lock().clone();
304        fidl_channel.flush_timeout = flush_timeout.map(zx::MonotonicDuration::into_nanos);
305
306        fidl_channel.ext_direction = channel
307            .audio_direction_ext
308            .map(|proxy| {
309                let chan = proxy.into_channel()?;
310                Ok(ClientEnd::new(chan.into()))
311            })
312            .transpose()
313            .map_err(|_: bredr::AudioDirectionExtProxy| {
314                Error::profile("AudioDirection proxy in use")
315            })?;
316
317        fidl_channel.ext_l2cap = channel
318            .l2cap_parameters_ext
319            .map(|proxy| {
320                let chan = proxy.into_channel()?;
321                Ok(ClientEnd::new(chan.into()))
322            })
323            .transpose()
324            .map_err(|_: bredr::L2capParametersExtProxy| {
325                Error::profile("l2cap parameters proxy in use")
326            })?;
327
328        fidl_channel.ext_audio_offload = channel
329            .audio_offload_ext
330            .map(|proxy| {
331                let chan = proxy.into_channel()?;
332                Ok(ClientEnd::new(chan.into()))
333            })
334            .transpose()
335            .map_err(|_: bredr::AudioOffloadExtProxy| {
336                Error::profile("audio offload proxy in use")
337            })?;
338
339        Ok(fidl_channel)
340    }
341}
342
343impl TryFrom<fidl_fuchsia_bluetooth_bredr::Channel> for Channel {
344    type Error = zx::Status;
345
346    fn try_from(fidl: bredr::Channel) -> Result<Self, Self::Error> {
347        let mode = match fidl.channel_mode.unwrap_or(fidl_bt::ChannelMode::Basic).try_into() {
348            Err(e) => {
349                warn!("Unsupported channel mode type: {e:?}");
350                return Err(zx::Status::INTERNAL);
351            }
352            Ok(c) => c,
353        };
354
355        let socket = fidl.socket.ok_or(zx::Status::INVALID_ARGS)?;
356        let connection = Box::new(SocketConnection::new(socket));
357
358        Ok(Self {
359            connection,
360            mode,
361            max_tx_size: fidl.max_tx_sdu_size.ok_or(zx::Status::INVALID_ARGS)? as usize,
362            flush_timeout: Arc::new(Mutex::new(
363                fidl.flush_timeout.map(zx::MonotonicDuration::from_nanos),
364            )),
365            audio_direction_ext: fidl.ext_direction.map(|e| e.into_proxy()),
366            l2cap_parameters_ext: fidl.ext_l2cap.map(|e| e.into_proxy()),
367            audio_offload_ext: fidl.ext_audio_offload.map(|c| c.into_proxy()),
368            terminated: false,
369        })
370    }
371}