1use fuchsia_bluetooth::profile::ValidScoConnectionParameters;
6use fuchsia_inspect as inspect;
7use fuchsia_inspect_derive::{IValue, Unit};
8use futures::future::{BoxFuture, Fuse, FusedFuture, Future, Shared};
9use futures::FutureExt;
10use std::fmt;
11use vigil::Vigil;
12
13use crate::a2dp;
14use crate::sco::{ConnectError, Connection};
15
16#[derive(Debug)]
17pub struct Active {
18 pub params: ValidScoConnectionParameters,
19 on_closed: Shared<BoxFuture<'static, ()>>,
20 _pause_token: a2dp::PauseToken,
22}
23
24impl Active {
25 pub fn new(connection: &Connection, pause_token: a2dp::PauseToken) -> Self {
26 let on_closed = connection.on_closed().boxed().shared();
27 Self { params: connection.params.clone(), on_closed, _pause_token: pause_token }
28 }
29
30 pub fn on_closed(&self) -> impl Future<Output = ()> + 'static {
31 self.on_closed.clone()
32 }
33}
34
35impl Unit for Active {
36 type Data = <Connection as Unit>::Data;
37 fn inspect_create(&self, parent: &inspect::Node, name: impl AsRef<str>) -> Self::Data {
38 self.params.inspect_create(parent, name)
39 }
40
41 fn inspect_update(&self, data: &mut Self::Data) {
42 self.params.inspect_update(data)
43 }
44}
45
46#[derive(Default)]
47pub enum State {
48 #[default]
50 Inactive,
51 SettingUp,
56 TearingDown,
61 AwaitingRemote(BoxFuture<'static, Result<Connection, ConnectError>>),
63 Active(Vigil<Active>),
65}
66
67impl State {
68 pub fn is_active(&self) -> bool {
69 match self {
70 Self::Active(_) => true,
71 _ => false,
72 }
73 }
74
75 pub fn on_connected<'a>(
76 &'a mut self,
77 ) -> impl Future<Output = Result<Connection, ConnectError>> + FusedFuture + 'a {
78 match self {
79 Self::AwaitingRemote(ref mut fut) => fut.fuse(),
80 _ => Fuse::terminated(),
81 }
82 }
83
84 pub fn on_closed<'a>(&'a self) -> impl Future<Output = ()> + FusedFuture + 'static {
85 match self {
86 Self::Active(ref connection) => connection.on_closed().fuse(),
87 _ => Fuse::terminated(),
88 }
89 }
90}
91
92impl fmt::Debug for State {
93 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
94 match self {
95 State::Inactive => write!(f, "Inactive"),
96 State::SettingUp => write!(f, "SettingUp"),
97 State::TearingDown => write!(f, "TearingDown"),
98 State::AwaitingRemote(_) => write!(f, "AwaitingRemote"),
99 State::Active(active) => write!(f, "Active({:?})", active),
100 }
101 }
102}
103
104impl std::fmt::Display for State {
105 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106 let state = match &self {
107 State::Inactive => "Inactive",
108 State::SettingUp => "SettingUp",
109 State::TearingDown => "TearingDown",
110 State::AwaitingRemote(_) => "AwaitingRemote",
111 State::Active(_) => "Active",
112 };
113 write!(f, "{}", state)
114 }
115}
116
117impl Unit for State {
118 type Data = inspect::Node;
119 fn inspect_create(&self, parent: &inspect::Node, name: impl AsRef<str>) -> Self::Data {
120 let mut node = parent.create_child(String::from(name.as_ref()));
121 self.inspect_update(&mut node);
122 node
123 }
124
125 fn inspect_update(&self, data: &mut Self::Data) {
126 data.clear_recorded();
127 data.record_string("status", &format!("{}", self));
128 if let State::Active(active) = &self {
129 let node = active.inspect_create(data, "parameters");
130 data.record(node);
131 }
132 }
133}
134
135pub type InspectableState = IValue<State>;
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 use diagnostics_assertions::assert_data_tree;
142 use fuchsia_bluetooth::types::PeerId;
143 use fuchsia_inspect_derive::WithInspect;
144 use {fidl_fuchsia_bluetooth as fidl_bt, fidl_fuchsia_bluetooth_bredr as bredr};
145
146 #[fuchsia::test]
147 async fn sco_state_inspect_tree() {
148 let inspect = inspect::Inspector::default();
149
150 let mut state =
151 InspectableState::default().with_inspect(inspect.root(), "sco_connection").unwrap();
152 assert_data_tree!(inspect, root: {
154 sco_connection: {
155 status: "Inactive",
156 }
157 });
158
159 state.iset(State::SettingUp);
160 assert_data_tree!(inspect, root: {
161 sco_connection: {
162 status: "SettingUp",
163 }
164 });
165
166 state.iset(State::AwaitingRemote(Box::pin(async { Err(ConnectError::Failed) })));
167 assert_data_tree!(inspect, root: {
168 sco_connection: {
169 status: "AwaitingRemote",
170 }
171 });
172
173 let params = bredr::ScoConnectionParameters {
174 parameter_set: Some(bredr::HfpParameterSet::D1),
175 air_coding_format: Some(fidl_bt::AssignedCodingFormat::Cvsd),
176 air_frame_size: Some(60),
177 io_bandwidth: Some(16000),
178 io_coding_format: Some(fidl_bt::AssignedCodingFormat::LinearPcm),
179 io_frame_size: Some(16),
180 io_pcm_data_format: Some(fidl_fuchsia_hardware_audio::SampleFormat::PcmSigned),
181 io_pcm_sample_payload_msb_position: Some(1),
182 path: Some(bredr::DataPath::Offload),
183 ..Default::default()
184 };
185 let (sco_proxy, _sco_stream) =
186 fidl::endpoints::create_proxy_and_stream::<bredr::ScoConnectionMarker>();
187 let connection = Connection::build(PeerId(1), params, sco_proxy);
188 let vigil = Vigil::new(Active::new(&connection, None));
189 state.iset(State::Active(vigil));
190 assert_data_tree!(inspect, root: {
191 sco_connection: {
192 status: "Active",
193 parameters: {
194 parameter_set: "D1",
195 air_coding_format: "Cvsd",
196 air_frame_size: 60u64,
197 io_bandwidth: 16000u64,
198 io_coding_format: "LinearPcm",
199 io_frame_size: 16u64,
200 io_pcm_data_format: "PcmSigned",
201 io_pcm_sample_payload_msb_position: 1u64,
202 path: "Offload",
203 },
204 }
205 });
206
207 state.iset(State::TearingDown);
208 assert_data_tree!(inspect, root: {
209 sco_connection: {
210 status: "TearingDown",
211 }
212 });
213 }
214}