emergency_lib/
bss_resolver.rs

1// Copyright 2020 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 crate::bss_cache::{Bss, BssId};
6use async_trait::async_trait;
7use fidl::{Error as FidlError, Socket as FidlSocket};
8use fidl_fuchsia_location_position::{Position, PositionExtras};
9use fidl_fuchsia_mem::Buffer as MemBuffer;
10use fidl_fuchsia_net_http::{
11    Body as HttpBody, LoaderProxyInterface, Request as HttpRequest, Response as HttpResponse,
12};
13
14use futures::io::AsyncReadExt;
15use futures::Future;
16use itertools::Itertools;
17use serde_json::json;
18use serde_json::map::Map as JsonMap;
19use serde_json::value::Value as JsonValue;
20use static_assertions::assert_eq_size_val;
21use std::borrow::Borrow;
22
23// Geographic constants.
24const LATITUDE_RANGE: std::ops::RangeInclusive<f64> = -90.0..=90.0;
25const LONGITUDE_RANGE: std::ops::RangeInclusive<f64> = -180.0..=180.0;
26
27// Google Maps constants.
28const SERVICE_URL: &'static str = "https://www.googleapis.com/geolocation/v1/geolocate";
29const MAC_ADDR_KEY: &'static str = "macAddress";
30const AP_LIST_KEY: &'static str = "wifiAccessPoints";
31const RSSI_KEY: &'static str = "signalStrength";
32const LOCATION_KEY: &'static str = "location";
33const LATITUDE_KEY: &'static str = "lat";
34const LONGITUDE_KEY: &'static str = "lng";
35const ACCURACY_KEY: &'static str = "accuracy";
36
37// HTTP constants.
38const HTTP_METHOD_POST: &'static str = "POST";
39
40#[async_trait(?Send)]
41pub trait BssResolver {
42    /// Resolves WLAN BSS (base-station) meta-data to a `Position`.
43    async fn resolve<'a, I, T, U>(&self, bsses: I) -> Result<Position, ResolverError>
44    where
45        I: IntoIterator,
46        I::Item: Borrow<(T, U)>,
47        T: Borrow<BssId>,
48        U: Borrow<Bss>;
49}
50
51/// A service for resolving WLAN BSS (base-station) meta-data to `Position`s.
52pub struct RealBssResolver<L: LoaderProxyInterface> {
53    http_loader: L,
54    api_key: String,
55}
56
57#[derive(Clone, Copy, Debug, PartialEq)]
58pub enum ResolverError {
59    NoBsses,
60    Internal,
61    Lookup,
62}
63
64impl<L: LoaderProxyInterface> RealBssResolver<L> {
65    pub fn new(http_loader: L, api_key: impl Into<String>) -> Self {
66        Self { http_loader, api_key: api_key.into() }
67    }
68}
69
70#[async_trait(?Send)]
71impl<L: LoaderProxyInterface> BssResolver for RealBssResolver<L> {
72    async fn resolve<'a, I, T, U>(&self, bsses: I) -> Result<Position, ResolverError>
73    where
74        I: IntoIterator,
75        I::Item: Borrow<(T, U)>,
76        T: Borrow<BssId>,
77        U: Borrow<Bss>,
78    {
79        let mut bsses = bsses.into_iter().peekable();
80        if bsses.peek().is_none() {
81            return Err(ResolverError::NoBsses);
82        }
83        parse_response(send_query(bsses, &self.api_key, &self.http_loader)?.await).await
84    }
85}
86
87fn send_query<'a, I, T, U>(
88    bsses: I,
89    api_key: impl AsRef<str>,
90    http_loader: &impl LoaderProxyInterface,
91) -> Result<impl Future<Output = Result<HttpResponse, FidlError>>, ResolverError>
92where
93    I: Iterator,
94    I::Item: Borrow<(T, U)>,
95    T: Borrow<BssId>,
96    U: Borrow<Bss>,
97{
98    let api_key = api_key.as_ref();
99    let request_body = serialize_bsses(bsses);
100    let request_size = {
101        let body_size = request_body.len();
102        assert_eq_size_val!(body_size, 0u64);
103        u64::try_from(body_size).expect("failed to convert usize to u64")
104    };
105    let request_vmo = zx::Vmo::create(request_size).map_err(|_| ResolverError::Internal)?;
106    let _ = request_vmo.write(&request_body.as_bytes(), 0).map_err(|_| ResolverError::Internal)?;
107    Ok(http_loader.fetch(HttpRequest {
108        method: Some(HTTP_METHOD_POST.to_owned()),
109        url: Some(format!("{}?key={}", SERVICE_URL, api_key)),
110        headers: None,
111        body: Some(HttpBody::Buffer(MemBuffer { vmo: request_vmo, size: request_size })),
112        deadline: None,
113        ..Default::default()
114    }))
115}
116
117async fn parse_response(
118    response: Result<HttpResponse, FidlError>,
119) -> Result<Position, ResolverError> {
120    let response: HttpResponse = response.map_err(|_fidl_error| ResolverError::Internal)?;
121    let response: FidlSocket = response.body.ok_or(ResolverError::Lookup)?;
122    let response: String = read_socket(response).await.ok_or(ResolverError::Lookup)?;
123    json_to_position(serde_json::from_str(&response).map_err(|_| ResolverError::Lookup)?)
124}
125
126fn serialize_bsses<'a, I, T, U>(bsses: I) -> String
127where
128    I: Iterator,
129    I::Item: Borrow<(T, U)>,
130    T: Borrow<BssId>,
131    U: Borrow<Bss>,
132{
133    let bsses = bsses
134        .map(|item| bss_to_json(item.borrow().0.borrow(), item.borrow().1.borrow()))
135        .collect::<Vec<_>>();
136    json!({ AP_LIST_KEY: bsses }).to_string()
137}
138
139fn bss_to_json(bss_id: impl Borrow<BssId>, bss: impl Borrow<Bss>) -> JsonValue {
140    let mut json = JsonMap::new();
141    json.insert(
142        MAC_ADDR_KEY.to_owned(),
143        json!(bss_id.borrow().iter().map(|bss_byte| format!("{:02x}", bss_byte)).join(":")),
144    );
145    if let Some(rssi) = bss.borrow().rssi {
146        json.insert(RSSI_KEY.to_owned(), json!(rssi));
147    };
148    JsonValue::from(json)
149}
150
151async fn read_socket(socket: zx::Socket) -> Option<String> {
152    let mut buf = Vec::new();
153    let mut socket = fuchsia_async::Socket::from_socket(socket);
154    match socket.read_to_end(&mut buf).await {
155        Ok(_num_bytes_read) => String::from_utf8(buf).ok(),
156        Err(_) => None,
157    }
158}
159
160fn json_to_position(response: JsonValue) -> Result<Position, ResolverError> {
161    let latitude = response[LOCATION_KEY][LATITUDE_KEY].as_f64().ok_or(ResolverError::Lookup)?;
162    if !LATITUDE_RANGE.contains(&latitude) {
163        return Err(ResolverError::Lookup);
164    }
165
166    let longitude = response[LOCATION_KEY][LONGITUDE_KEY].as_f64().ok_or(ResolverError::Lookup)?;
167    if !LONGITUDE_RANGE.contains(&longitude) {
168        return Err(ResolverError::Lookup);
169    }
170
171    let extras = match response[ACCURACY_KEY].as_f64() {
172        Some(accuracy) => PositionExtras {
173            accuracy_meters: Some(accuracy),
174            altitude_meters: None,
175            ..Default::default()
176        },
177        None => {
178            PositionExtras { accuracy_meters: None, altitude_meters: None, ..Default::default() }
179        }
180    };
181
182    Ok(Position { latitude, longitude, extras })
183}
184
185#[cfg(test)]
186mod tests {
187    mod request_generation {
188        use super::super::test_doubles::HttpRequestValidator;
189        use super::super::*;
190        use fuchsia_async as fasync;
191
192        #[fasync::run_until_stalled(test)]
193        async fn request_uses_post_method() {
194            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: Some(2412) })];
195            let mut request_method = None;
196            let http_loader = HttpRequestValidator::new(|request| {
197                request_method = Some(request.method.expect("request had no method"));
198            });
199            let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
200            assert_eq!(request_method.expect("request was never issued"), "POST".to_owned());
201        }
202
203        #[fasync::run_until_stalled(test)]
204        async fn request_url_is_correct() {
205            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: Some(2412) })];
206            let mut request_url = None;
207            let http_loader = HttpRequestValidator::new(|request| {
208                request_url = Some(request.url.expect("request had no url"));
209            });
210            let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
211            assert_eq!(
212                request_url.expect("request was never issued"),
213                "https://www.googleapis.com/geolocation/v1/geolocate?key=fake_key".to_owned()
214            );
215        }
216
217        #[fasync::run_until_stalled(test)]
218        async fn request_body_contains_all_bsses() {
219            let bsses = vec![
220                ([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None }),
221                ([1, 1, 1, 1, 1, 1], Bss { rssi: None, frequency: None }),
222            ];
223            let mut request_body = None;
224            let http_loader = HttpRequestValidator::new(|request| {
225                request_body = Some(request.body.expect("request had no body"));
226            });
227            let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
228            assert_eq!(
229                strip_whitespace(read_request_body(
230                    request_body.expect("request was never issued")
231                )),
232                strip_whitespace(
233                    r#"{
234                    "wifiAccessPoints":[
235                        {"macAddress":"00:00:00:00:00:00"},
236                        {"macAddress":"01:01:01:01:01:01"}
237                    ]
238                }"#
239                )
240            );
241        }
242
243        #[fasync::run_until_stalled(test)]
244        async fn request_body_includes_rssi() {
245            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: None })];
246            let mut request_body = None;
247            let http_loader = HttpRequestValidator::new(|request| {
248                request_body = Some(request.body.expect("request had no body"));
249            });
250            let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
251            assert_eq!(
252                strip_whitespace(read_request_body(
253                    request_body.expect("request was never issued")
254                )),
255                strip_whitespace(
256                    r#"{
257                    "wifiAccessPoints":[
258                        {"macAddress":"00:00:00:00:00:00",
259                         "signalStrength": -30}
260                    ]
261                }"#
262                )
263            )
264        }
265
266        #[fasync::run_until_stalled(test)]
267        async fn skips_api_query_when_bsses_is_empty() {
268            let http_loader =
269                HttpRequestValidator::new(|_request| panic!("should not issue API query"));
270            let _ = RealBssResolver::new(http_loader, "fake_key")
271                .resolve(std::iter::empty::<(BssId, Bss)>())
272                .await;
273        }
274
275        #[fasync::run_until_stalled(test)]
276        async fn returns_no_bsses_error_when_bsses_is_empty() {
277            let http_loader = HttpRequestValidator::new(|_| ());
278            assert_eq!(
279                RealBssResolver::new(http_loader, "fake_key")
280                    .resolve(std::iter::empty::<(BssId, Bss)>())
281                    .await,
282                Err(ResolverError::NoBsses)
283            );
284        }
285
286        fn read_request_body(body: HttpBody) -> String {
287            match body {
288                HttpBody::Buffer(shared_buf) => {
289                    let mut local_buf = vec![
290                        0_u8;
291                        usize::try_from(shared_buf.size).expect(
292                            "internal error: failed to convert u64 to usize"
293                        )
294                    ];
295                    shared_buf
296                        .vmo
297                        .read(&mut local_buf, 0)
298                        .expect("internal error: failed to read from VMO");
299                    String::from_utf8(local_buf)
300                        .expect("internal error: failed to convert buffer to UTF-8")
301                }
302                HttpBody::Stream(_) => panic!("internal error: stream bodies not supported"),
303            }
304        }
305
306        fn strip_whitespace<S: AsRef<str>>(input: S) -> String {
307            input.as_ref().replace("\n", "").replace(" ", "")
308        }
309    }
310
311    mod response_error_handling {
312        use super::super::test_doubles::{HttpByteResponder, HttpFidlResponder};
313        use super::super::*;
314        use fuchsia_async as fasync;
315
316        #[fasync::run_until_stalled(test)]
317        async fn returns_internal_error_on_fidl_error() {
318            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
319            let http_loader = HttpFidlResponder::new(|| Err(FidlError::InvalidHeader));
320            assert_eq!(
321                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
322                Err(ResolverError::Internal)
323            );
324        }
325
326        #[fasync::run_until_stalled(test)]
327        async fn returns_lookup_error_when_response_has_no_body() {
328            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
329            let http_loader = HttpFidlResponder::new(|| {
330                Ok(HttpResponse {
331                    error: None,
332                    body: None,
333                    final_url: None,
334                    status_code: None,
335                    status_line: None,
336                    headers: None,
337                    redirect: None,
338                    ..Default::default()
339                })
340            });
341            assert_eq!(
342                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
343                Err(ResolverError::Lookup)
344            );
345        }
346
347        #[fasync::run_until_stalled(test)]
348        async fn returns_lookup_error_when_body_is_unreadable() {
349            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
350            let http_loader = HttpFidlResponder::new(|| {
351                Ok(HttpResponse {
352                    error: None,
353                    body: Some(zx::Socket::from(zx::Handle::invalid())),
354                    final_url: None,
355                    status_code: None,
356                    status_line: None,
357                    headers: None,
358                    redirect: None,
359                    ..Default::default()
360                })
361            });
362            assert_eq!(
363                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
364                Err(ResolverError::Lookup)
365            );
366        }
367
368        #[fasync::run_until_stalled(test)]
369        async fn returns_lookup_error_when_body_is_not_valid_json() {
370            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
371            let http_loader = HttpByteResponder::new(b"hello world".to_vec());
372            assert_eq!(
373                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
374                Err(ResolverError::Lookup)
375            );
376        }
377
378        #[fasync::run_until_stalled(test)]
379        async fn returns_lookup_error_when_body_is_not_a_dictionary() {
380            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
381            let http_loader = HttpByteResponder::new(b"42".to_vec());
382            assert_eq!(
383                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
384                Err(ResolverError::Lookup)
385            );
386        }
387
388        #[fasync::run_until_stalled(test)]
389        async fn returns_lookup_error_when_body_is_missing_location() {
390            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
391            let http_loader = HttpByteResponder::new(b"{}".to_vec());
392            assert_eq!(
393                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
394                Err(ResolverError::Lookup)
395            );
396        }
397
398        #[fasync::run_until_stalled(test)]
399        async fn returns_lookup_error_when_body_is_missing_latitude() {
400            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
401            let http_loader = HttpByteResponder::new(
402                br#"{
403                        "location": {
404                          "lng": -0.1
405                        },
406                        "accuracy": 1200.4
407                }"#
408                .to_vec(),
409            );
410            assert_eq!(
411                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
412                Err(ResolverError::Lookup)
413            );
414        }
415
416        #[fasync::run_until_stalled(test)]
417        async fn returns_lookup_error_when_body_is_missing_longitude() {
418            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
419            let http_loader = HttpByteResponder::new(
420                br#"{
421                        "location": {
422                            "lat": 51.0,
423                        },
424                        "accuracy": 1200.4
425                }"#
426                .to_vec(),
427            );
428            assert_eq!(
429                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
430                Err(ResolverError::Lookup)
431            );
432        }
433
434        #[fasync::run_until_stalled(test)]
435        async fn returns_lookup_error_when_latitude_is_too_high() {
436            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
437            let http_loader = HttpByteResponder::new(
438                br#"{
439                        "location": {
440                            "lat": 90.1,
441                            "lng": 0.0
442                        }
443                }"#
444                .to_vec(),
445            );
446            assert_eq!(
447                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
448                Err(ResolverError::Lookup)
449            );
450        }
451
452        #[fasync::run_until_stalled(test)]
453        async fn returns_lookup_error_when_latitude_is_too_low() {
454            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
455            let http_loader = HttpByteResponder::new(
456                br#"{
457                        "location": {
458                            "lat": -90.1,
459                            "lng": 0.0
460                        }
461                }"#
462                .to_vec(),
463            );
464            assert_eq!(
465                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
466                Err(ResolverError::Lookup)
467            );
468        }
469
470        #[fasync::run_until_stalled(test)]
471        async fn returns_lookup_error_when_longitude_is_too_high() {
472            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
473            let http_loader = HttpByteResponder::new(
474                br#"{
475                        "location": {
476                            "lat": 0.0,
477                            "lng": 180.1
478                        }
479                }"#
480                .to_vec(),
481            );
482            assert_eq!(
483                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
484                Err(ResolverError::Lookup)
485            );
486        }
487
488        #[fasync::run_until_stalled(test)]
489        async fn returns_lookup_error_when_longitude_is_too_low() {
490            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
491            let http_loader = HttpByteResponder::new(
492                br#"{
493                        "location": {
494                            "lat": 0.0,
495                            "lng": -180.1
496                        }
497                }"#
498                .to_vec(),
499            );
500            assert_eq!(
501                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
502                Err(ResolverError::Lookup)
503            );
504        }
505    }
506
507    mod response_success_reporting {
508        use super::super::test_doubles::HttpByteResponder;
509        use super::super::*;
510        use assert_matches::assert_matches;
511        use fuchsia_async as fasync;
512
513        #[fasync::run_until_stalled(test)]
514        async fn returns_success_when_all_fields_are_present() {
515            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
516            let http_loader = HttpByteResponder::new(
517                br#"{
518                        "location": {
519                            "lat": 51.0,
520                            "lng": -0.1
521                        },
522                        "accuracy": 1200.4
523                }"#
524                .to_vec(),
525            );
526            assert_matches!(
527                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
528                Ok(_)
529            );
530        }
531
532        #[fasync::run_until_stalled(test)]
533        async fn returns_success_when_all_fields_except_accuracy_are_present() {
534            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
535            let http_loader = HttpByteResponder::new(
536                br#"{
537                        "location": {
538                            "lat": 51.0,
539                            "lng": -0.1
540                       }
541                }"#
542                .to_vec(),
543            );
544            assert_matches!(
545                RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
546                Ok(_)
547            );
548        }
549    }
550
551    mod response_success_contents {
552        use super::super::test_doubles::HttpByteResponder;
553        use super::super::*;
554        use assert_matches::assert_matches;
555        use fuchsia_async as fasync;
556
557        #[fasync::run_until_stalled(test)]
558        async fn provides_precise_latitude() {
559            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
560            let http_loader = HttpByteResponder::new(
561                br#"{
562                        "location": {
563                            "lat": 89.0,
564                            "lng": 0
565                       }
566                }"#
567                .to_vec(),
568            );
569            assert_matches!(
570                RealBssResolver::new(http_loader, "fake_key")
571                    .resolve(bsses)
572                    .await.expect("position is none").latitude,
573                // One degree of latitude is approximately 111 kilometers. By limiting rounding
574                // error to 1/10,000,000 of a degree, we limit rounding error to
575                // 111/10000 = 0.011 meters, or  1.1 cm.
576                latitude if (88.999_999_9..89.000_000_1).contains(&latitude)
577            );
578        }
579
580        #[fasync::run_until_stalled(test)]
581        async fn provides_precise_longitude() {
582            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
583            let http_loader = HttpByteResponder::new(
584                br#"{
585                        "location": {
586                            "lat": 0,
587                            "lng": 179.0
588                       }
589                }"#
590                .to_vec(),
591            );
592            assert_matches!(
593                RealBssResolver::new(http_loader, "fake_key")
594                    .resolve(bsses)
595                    .await.expect("no position").longitude,
596                // At the equator, one degree of longitude is approximately 111 kilometers.
597                // By limiting rounding error to 1/10,000,000 of a degree, we limit rounding error
598                // to 111/10000 = 0.011 meters, or  1.1 cm.
599                longitude if (178.999_999_9..179.099_999_9).contains(&longitude)
600            );
601        }
602
603        #[fasync::run_until_stalled(test)]
604        async fn provides_precise_accuracy_when_present() {
605            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
606            let http_loader = HttpByteResponder::new(
607                br#"{
608                        "location": {
609                            "lat": 51.0,
610                            "lng": -0.1
611                        },
612                        "accuracy": 1200.4
613                }"#
614                .to_vec(),
615            );
616            assert_matches!(
617                RealBssResolver::new(http_loader, "fake_key")
618                    .resolve(bsses)
619                    .await.expect("no position").extras.accuracy_meters.expect("accuracy is none"),
620                accuracy if (1200.39..1200.41).contains(&accuracy)
621            );
622        }
623
624        #[fasync::run_until_stalled(test)]
625        async fn does_not_fabricate_accuracy() {
626            let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
627            let http_loader = HttpByteResponder::new(
628                br#"{
629                        "location": {
630                            "lat": 51.0,
631                            "lng": -0.1
632                        }
633                }"#
634                .to_vec(),
635            );
636            assert_eq!(
637                RealBssResolver::new(http_loader, "fake_key")
638                    .resolve(bsses)
639                    .await
640                    .expect("no position")
641                    .extras
642                    .accuracy_meters,
643                None
644            );
645        }
646    }
647}
648
649#[cfg(test)]
650mod test_doubles {
651    use super::*;
652    use fidl::endpoints::ClientEnd;
653    use fidl_fuchsia_net_http::LoaderClientMarker;
654    use futures::future::{ready, Ready};
655    use std::sync::RwLock;
656
657    const HTTP_OK: u32 = 200;
658
659    type FetchResponse = Result<HttpResponse, FidlError>;
660
661    // Test double that
662    // 1) invokes `validate` to run test assertions, and
663    // 2) returns HTTP_OK, assuming `validate` did not abort
664    pub(super) struct HttpRequestValidator<F>
665    where
666        F: FnMut(HttpRequest) + Send + Sync,
667    {
668        // RwLock is needed because
669        // a) we need interior mutability for F to be FnMut, and
670        // b) we need to implement Send and Sync.
671        validate: RwLock<F>,
672    }
673
674    // Test double that invokes a function which yields `FetchResponse`,
675    // and returns that as a FIDL response. Useful for testing error
676    // handling.
677    pub(super) struct HttpFidlResponder<F>
678    where
679        F: Fn() -> FetchResponse + Send + Sync,
680    {
681        fetch: F,
682    }
683
684    // Test double that invokes a function which yields a `Vec<u8>`,
685    // and returns the Vec as the HTTP response. Useful for testing
686    // response parsing.
687    pub(super) struct HttpByteResponder {
688        response: Vec<u8>,
689    }
690
691    impl<F> HttpRequestValidator<F>
692    where
693        F: FnMut(HttpRequest) + Send + Sync,
694    {
695        pub(super) fn new(validate: F) -> Self {
696            Self { validate: RwLock::new(validate) }
697        }
698    }
699
700    impl<F> LoaderProxyInterface for HttpRequestValidator<F>
701    where
702        F: FnMut(HttpRequest) + Send + Sync,
703    {
704        type FetchResponseFut = Ready<Result<HttpResponse, FidlError>>;
705
706        fn fetch(&self, request: HttpRequest) -> Self::FetchResponseFut {
707            // Note: the `&mut *` here is due to https://github.com/rust-lang/rust/issues/65489
708            let validate = &mut *self.validate.write().expect("internal error");
709            let final_url = request.url.clone();
710            validate(request);
711            ready(Ok(HttpResponse {
712                error: None,
713                body: None,
714                final_url,
715                status_code: Some(HTTP_OK),
716                status_line: None,
717                headers: None,
718                redirect: None,
719                ..Default::default()
720            }))
721        }
722
723        fn start(
724            &self,
725            _request: HttpRequest,
726            _client: ClientEnd<LoaderClientMarker>,
727        ) -> Result<(), FidlError> {
728            panic!("internal error: this fake does not implement `start()`");
729        }
730    }
731
732    impl<F> HttpFidlResponder<F>
733    where
734        F: Fn() -> FetchResponse + Send + Sync,
735    {
736        pub(super) fn new(fetch: F) -> Self {
737            Self { fetch }
738        }
739    }
740
741    impl<F> LoaderProxyInterface for HttpFidlResponder<F>
742    where
743        F: Fn() -> FetchResponse + Send + Sync,
744    {
745        type FetchResponseFut = Ready<FetchResponse>;
746
747        fn fetch(&self, _request: HttpRequest) -> Self::FetchResponseFut {
748            ready((self.fetch)())
749        }
750
751        fn start(
752            &self,
753            _request: HttpRequest,
754            _client: ClientEnd<LoaderClientMarker>,
755        ) -> Result<(), FidlError> {
756            panic!("internal error: this stub does not implement `start()`");
757        }
758    }
759
760    impl HttpByteResponder {
761        pub(super) fn new(response: Vec<u8>) -> Self {
762            Self { response }
763        }
764    }
765
766    impl LoaderProxyInterface for HttpByteResponder {
767        type FetchResponseFut = Ready<FetchResponse>;
768
769        fn fetch(&self, _request: HttpRequest) -> Self::FetchResponseFut {
770            let (local_socket, remote_socket) = zx::Socket::create_stream();
771            local_socket.write(&self.response).expect("internal error");
772            ready(Ok(HttpResponse {
773                error: None,
774                body: Some(remote_socket),
775                final_url: None,
776                status_code: Some(HTTP_OK),
777                status_line: None,
778                headers: None,
779                redirect: None,
780                ..Default::default()
781            }))
782        }
783
784        fn start(
785            &self,
786            _request: HttpRequest,
787            _client: ClientEnd<LoaderClientMarker>,
788        ) -> Result<(), FidlError> {
789            panic!("internal error: this stub does not implement `start()`");
790        }
791    }
792}