realflight_bridge/bridge/local/
async_impl.rs1use std::net::SocketAddr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use crate::bridge::AsyncBridge;
8use crate::soap_client::AsyncSoapClient;
9use crate::soap_client::tcp_async::AsyncTcpSoapClient;
10use crate::{BridgeError, ControlInputs, SimulatorState, Statistics, StatisticsEngine};
11
12use super::encode_control_inputs;
13
14const EMPTY_BODY: &str = "";
15const DEFAULT_CONNECT_TIMEOUT: Duration = Duration::from_millis(5);
16const DEFAULT_INIT_TIMEOUT: Duration = Duration::from_secs(5);
17const DEFAULT_POOL_SIZE: usize = 1;
19
20#[derive(Debug, Clone)]
24pub struct AsyncLocalBridgeBuilder {
25 connect_timeout: Duration,
26 init_timeout: Duration,
27 addr: SocketAddr,
28 pool_size: usize,
29}
30
31impl Default for AsyncLocalBridgeBuilder {
32 fn default() -> Self {
33 Self {
34 connect_timeout: DEFAULT_CONNECT_TIMEOUT,
35 init_timeout: DEFAULT_INIT_TIMEOUT,
36 addr: crate::DEFAULT_SIMULATOR_HOST.parse().unwrap(),
37 pool_size: DEFAULT_POOL_SIZE,
38 }
39 }
40}
41
42impl AsyncLocalBridgeBuilder {
43 pub fn new() -> Self {
45 Self::default()
46 }
47
48 #[must_use]
50 pub fn connect_timeout(mut self, timeout: Duration) -> Self {
51 self.connect_timeout = timeout;
52 self
53 }
54
55 #[must_use]
57 pub fn init_timeout(mut self, timeout: Duration) -> Self {
58 self.init_timeout = timeout;
59 self
60 }
61
62 #[must_use]
64 pub fn addr(mut self, addr: SocketAddr) -> Self {
65 self.addr = addr;
66 self
67 }
68
69 #[must_use]
71 pub fn pool_size(mut self, size: usize) -> Self {
72 self.pool_size = size;
73 self
74 }
75
76 pub async fn build(self) -> Result<AsyncLocalBridge, BridgeError> {
78 let statistics = Arc::new(StatisticsEngine::new());
79 let soap_client = AsyncTcpSoapClient::new(
80 self.addr,
81 self.connect_timeout,
82 self.pool_size,
83 statistics.clone(),
84 )
85 .await?;
86
87 soap_client
88 .ensure_pool_initialized(self.init_timeout)
89 .await?;
90
91 Ok(AsyncLocalBridge {
92 statistics,
93 soap_client,
94 })
95 }
96}
97
98pub struct AsyncLocalBridge {
130 statistics: Arc<StatisticsEngine>,
131 soap_client: AsyncTcpSoapClient,
132}
133
134impl AsyncBridge for AsyncLocalBridge {
135 async fn exchange_data(&self, control: &ControlInputs) -> Result<SimulatorState, BridgeError> {
136 let body = encode_control_inputs(control);
137 let response = self.soap_client.send_action("ExchangeData", &body).await?;
138 match response.status_code {
139 200 => crate::decoders::decode_simulator_state(&response.body),
140 _ => Err(BridgeError::SoapFault(response.fault_message())),
141 }
142 }
143
144 async fn enable_rc(&self) -> Result<(), BridgeError> {
145 self.soap_client
146 .send_action("RestoreOriginalControllerDevice", EMPTY_BODY)
147 .await?
148 .into()
149 }
150
151 async fn disable_rc(&self) -> Result<(), BridgeError> {
152 self.soap_client
153 .send_action("InjectUAVControllerInterface", EMPTY_BODY)
154 .await?
155 .into()
156 }
157
158 async fn reset_aircraft(&self) -> Result<(), BridgeError> {
159 self.soap_client
160 .send_action("ResetAircraft", EMPTY_BODY)
161 .await?
162 .into()
163 }
164}
165
166impl AsyncLocalBridge {
167 pub async fn new() -> Result<Self, BridgeError> {
169 AsyncLocalBridgeBuilder::default().build().await
170 }
171
172 pub fn builder() -> AsyncLocalBridgeBuilder {
174 AsyncLocalBridgeBuilder::default()
175 }
176
177 pub fn statistics(&self) -> Statistics {
179 self.statistics.snapshot()
180 }
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use crate::bridge::AsyncBridge;
187 use crate::tests::soap_stub::Server;
188 use approx::assert_relative_eq;
189 use std::net::TcpListener;
190
191 fn get_available_port() -> u16 {
192 TcpListener::bind("127.0.0.1:0")
193 .unwrap()
194 .local_addr()
195 .unwrap()
196 .port()
197 }
198
199 async fn create_bridge(port: u16) -> Result<AsyncLocalBridge, BridgeError> {
200 let addr: SocketAddr = format!("127.0.0.1:{}", port).parse().unwrap();
201 AsyncLocalBridge::builder()
202 .addr(addr)
203 .connect_timeout(Duration::from_millis(1000))
204 .init_timeout(Duration::from_secs(5))
205 .build()
206 .await
207 }
208
209 mod builder_tests {
214 use super::*;
215
216 #[test]
217 fn builder_default_connect_timeout() {
218 let builder = AsyncLocalBridgeBuilder::new();
219 assert_eq!(builder.connect_timeout, Duration::from_millis(5));
220 }
221
222 #[test]
223 fn builder_default_init_timeout() {
224 let builder = AsyncLocalBridgeBuilder::new();
225 assert_eq!(builder.init_timeout, Duration::from_secs(5));
226 }
227
228 #[test]
229 fn builder_default_pool_size() {
230 let builder = AsyncLocalBridgeBuilder::new();
231 assert_eq!(builder.pool_size, 1);
232 }
233
234 #[test]
235 fn builder_connect_timeout_sets_value() {
236 let builder =
237 AsyncLocalBridgeBuilder::new().connect_timeout(Duration::from_millis(100));
238 assert_eq!(builder.connect_timeout, Duration::from_millis(100));
239 }
240
241 #[test]
242 fn builder_init_timeout_sets_value() {
243 let builder = AsyncLocalBridgeBuilder::new().init_timeout(Duration::from_secs(10));
244 assert_eq!(builder.init_timeout, Duration::from_secs(10));
245 }
246
247 #[test]
248 fn builder_addr_sets_value() {
249 let addr: SocketAddr = "192.168.1.100:18083".parse().unwrap();
250 let builder = AsyncLocalBridgeBuilder::new().addr(addr);
251 assert_eq!(builder.addr, addr);
252 }
253
254 #[test]
255 fn builder_pool_size_sets_value() {
256 let builder = AsyncLocalBridgeBuilder::new().pool_size(5);
257 assert_eq!(builder.pool_size, 5);
258 }
259
260 #[test]
261 fn builder_is_cloneable() {
262 let builder = AsyncLocalBridgeBuilder::new()
263 .connect_timeout(Duration::from_millis(100))
264 .pool_size(3);
265 let cloned = builder.clone();
266 assert_eq!(cloned.connect_timeout, builder.connect_timeout);
267 assert_eq!(cloned.pool_size, builder.pool_size);
268 }
269 }
270
271 mod bridge_operations {
276 use super::*;
277
278 #[tokio::test]
279 async fn reset_aircraft_succeeds() {
280 let port = get_available_port();
281 let _server = Server::new(port, vec!["reset-aircraft-200".to_string()]);
282 let bridge = create_bridge(port).await.unwrap();
283
284 let result = bridge.reset_aircraft().await;
285 assert!(result.is_ok(), "expected Ok: {:?}", result);
286 }
287
288 #[tokio::test]
289 async fn reset_aircraft_increments_request_count() {
290 let port = get_available_port();
291 let _server = Server::new(port, vec!["reset-aircraft-200".to_string()]);
292 let bridge = create_bridge(port).await.unwrap();
293
294 bridge.reset_aircraft().await.unwrap();
295
296 let stats = bridge.statistics();
297 assert_eq!(stats.request_count, 1);
298 }
299
300 #[tokio::test]
301 async fn enable_rc_succeeds() {
302 let port = get_available_port();
303 let _server = Server::new(
304 port,
305 vec!["restore-original-controller-device-200".to_string()],
306 );
307 let bridge = create_bridge(port).await.unwrap();
308
309 let result = bridge.enable_rc().await;
310 assert!(result.is_ok(), "expected Ok: {:?}", result);
311 }
312
313 #[tokio::test]
314 async fn enable_rc_returns_soap_fault_on_500() {
315 let port = get_available_port();
316 let _server = Server::new(
317 port,
318 vec!["restore-original-controller-device-500".to_string()],
319 );
320 let bridge = create_bridge(port).await.unwrap();
321
322 let result = bridge.enable_rc().await;
323 match result {
324 Err(BridgeError::SoapFault(msg)) => {
325 assert_eq!(msg, "Pointer to original controller device is null");
326 }
327 other => panic!("expected SoapFault, got {:?}", other),
328 }
329 }
330
331 #[tokio::test]
332 async fn disable_rc_succeeds() {
333 let port = get_available_port();
334 let _server = Server::new(
335 port,
336 vec!["inject-uav-controller-interface-200".to_string()],
337 );
338 let bridge = create_bridge(port).await.unwrap();
339
340 let result = bridge.disable_rc().await;
341 assert!(result.is_ok(), "expected Ok: {:?}", result);
342 }
343
344 #[tokio::test]
345 async fn disable_rc_returns_soap_fault_on_500() {
346 let port = get_available_port();
347 let _server = Server::new(
348 port,
349 vec!["inject-uav-controller-interface-500".to_string()],
350 );
351 let bridge = create_bridge(port).await.unwrap();
352
353 let result = bridge.disable_rc().await;
354 match result {
355 Err(BridgeError::SoapFault(msg)) => {
356 assert_eq!(msg, "Preexisting controller reference");
357 }
358 other => panic!("expected SoapFault, got {:?}", other),
359 }
360 }
361 }
362
363 mod exchange_data {
368 use super::*;
369
370 #[tokio::test]
371 async fn returns_simulator_state_on_success() {
372 let port = get_available_port();
373 let _server = Server::new(port, vec!["return-data-200".to_string()]);
374 let bridge = create_bridge(port).await.unwrap();
375
376 let control = ControlInputs::default();
377 let result = bridge.exchange_data(&control).await;
378
379 assert!(result.is_ok(), "expected Ok: {:?}", result);
380 let state = result.unwrap();
381 assert_eq!(state.current_physics_speed_multiplier, 1.0);
382 }
383
384 #[tokio::test]
385 async fn returns_soap_fault_on_500() {
386 let port = get_available_port();
387 let _server = Server::new(port, vec!["return-data-500".to_string()]);
388 let bridge = create_bridge(port).await.unwrap();
389
390 let control = ControlInputs::default();
391 let result = bridge.exchange_data(&control).await;
392
393 match result {
394 Err(BridgeError::SoapFault(msg)) => {
395 assert_eq!(msg, "RealFlight Link controller has not been instantiated");
396 }
397 other => panic!("expected SoapFault, got {:?}", other),
398 }
399 }
400
401 #[tokio::test]
402 async fn parses_boolean_fields() {
403 let port = get_available_port();
404 let _server = Server::new(port, vec!["return-data-200".to_string()]);
405 let bridge = create_bridge(port).await.unwrap();
406
407 let state = bridge
408 .exchange_data(&ControlInputs::default())
409 .await
410 .unwrap();
411
412 assert!(!state.is_locked);
413 assert!(!state.has_lost_components);
414 assert!(state.an_engine_is_running);
415 assert!(!state.is_touching_ground);
416 assert!(state.flight_axis_controller_is_active);
417 }
418
419 #[cfg(not(feature = "uom"))]
420 #[tokio::test]
421 async fn parses_velocity_fields() {
422 let port = get_available_port();
423 let _server = Server::new(port, vec!["return-data-200".to_string()]);
424 let bridge = create_bridge(port).await.unwrap();
425
426 let state = bridge
427 .exchange_data(&ControlInputs::default())
428 .await
429 .unwrap();
430
431 assert_relative_eq!(state.airspeed, 0.040872246);
432 assert_relative_eq!(state.groundspeed, 4.643444754E-06);
433 }
434 }
435
436 mod statistics_tests {
441 use super::*;
442
443 #[tokio::test]
444 async fn statistics_returns_snapshot() {
445 let port = get_available_port();
446 let _server = Server::new(
447 port,
448 vec![
449 "reset-aircraft-200".to_string(),
450 "reset-aircraft-200".to_string(),
451 ],
452 );
453 let bridge = create_bridge(port).await.unwrap();
454
455 bridge.reset_aircraft().await.unwrap();
456 bridge.reset_aircraft().await.unwrap();
457
458 let stats = bridge.statistics();
459 assert_eq!(stats.request_count, 2);
460 assert_eq!(stats.error_count, 0);
461 }
462 }
463}