1use 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::Future;
15use futures::io::AsyncReadExt;
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
23const LATITUDE_RANGE: std::ops::RangeInclusive<f64> = -90.0..=90.0;
25const LONGITUDE_RANGE: std::ops::RangeInclusive<f64> = -180.0..=180.0;
26
27const 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
37const HTTP_METHOD_POST: &'static str = "POST";
39
40#[async_trait(?Send)]
41pub trait BssResolver {
42 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
51pub 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
191 #[fuchsia::test(allow_stalls = false)]
192 async fn request_uses_post_method() {
193 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: Some(2412) })];
194 let mut request_method = None;
195 let http_loader = HttpRequestValidator::new(|request| {
196 request_method = Some(request.method.expect("request had no method"));
197 });
198 let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
199 assert_eq!(request_method.expect("request was never issued"), "POST".to_owned());
200 }
201
202 #[fuchsia::test(allow_stalls = false)]
203 async fn request_url_is_correct() {
204 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: Some(2412) })];
205 let mut request_url = None;
206 let http_loader = HttpRequestValidator::new(|request| {
207 request_url = Some(request.url.expect("request had no url"));
208 });
209 let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
210 assert_eq!(
211 request_url.expect("request was never issued"),
212 "https://www.googleapis.com/geolocation/v1/geolocate?key=fake_key".to_owned()
213 );
214 }
215
216 #[fuchsia::test(allow_stalls = false)]
217 async fn request_body_contains_all_bsses() {
218 let bsses = vec![
219 ([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None }),
220 ([1, 1, 1, 1, 1, 1], Bss { rssi: None, frequency: None }),
221 ];
222 let mut request_body = None;
223 let http_loader = HttpRequestValidator::new(|request| {
224 request_body = Some(request.body.expect("request had no body"));
225 });
226 let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
227 assert_eq!(
228 strip_whitespace(read_request_body(
229 request_body.expect("request was never issued")
230 )),
231 strip_whitespace(
232 r#"{
233 "wifiAccessPoints":[
234 {"macAddress":"00:00:00:00:00:00"},
235 {"macAddress":"01:01:01:01:01:01"}
236 ]
237 }"#
238 )
239 );
240 }
241
242 #[fuchsia::test(allow_stalls = false)]
243 async fn request_body_includes_rssi() {
244 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: Some(-30), frequency: None })];
245 let mut request_body = None;
246 let http_loader = HttpRequestValidator::new(|request| {
247 request_body = Some(request.body.expect("request had no body"));
248 });
249 let _ = RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await;
250 assert_eq!(
251 strip_whitespace(read_request_body(
252 request_body.expect("request was never issued")
253 )),
254 strip_whitespace(
255 r#"{
256 "wifiAccessPoints":[
257 {"macAddress":"00:00:00:00:00:00",
258 "signalStrength": -30}
259 ]
260 }"#
261 )
262 )
263 }
264
265 #[fuchsia::test(allow_stalls = false)]
266 async fn skips_api_query_when_bsses_is_empty() {
267 let http_loader =
268 HttpRequestValidator::new(|_request| panic!("should not issue API query"));
269 let _ = RealBssResolver::new(http_loader, "fake_key")
270 .resolve(std::iter::empty::<(BssId, Bss)>())
271 .await;
272 }
273
274 #[fuchsia::test(allow_stalls = false)]
275 async fn returns_no_bsses_error_when_bsses_is_empty() {
276 let http_loader = HttpRequestValidator::new(|_| ());
277 assert_eq!(
278 RealBssResolver::new(http_loader, "fake_key")
279 .resolve(std::iter::empty::<(BssId, Bss)>())
280 .await,
281 Err(ResolverError::NoBsses)
282 );
283 }
284
285 fn read_request_body(body: HttpBody) -> String {
286 match body {
287 HttpBody::Buffer(shared_buf) => {
288 let mut local_buf = vec![
289 0_u8;
290 usize::try_from(shared_buf.size).expect(
291 "internal error: failed to convert u64 to usize"
292 )
293 ];
294 shared_buf
295 .vmo
296 .read(&mut local_buf, 0)
297 .expect("internal error: failed to read from VMO");
298 String::from_utf8(local_buf)
299 .expect("internal error: failed to convert buffer to UTF-8")
300 }
301 HttpBody::Stream(_) => panic!("internal error: stream bodies not supported"),
302 }
303 }
304
305 fn strip_whitespace<S: AsRef<str>>(input: S) -> String {
306 input.as_ref().replace("\n", "").replace(" ", "")
307 }
308 }
309
310 mod response_error_handling {
311 use super::super::test_doubles::{HttpByteResponder, HttpFidlResponder};
312 use super::super::*;
313
314 #[fuchsia::test(allow_stalls = false)]
315 async fn returns_internal_error_on_fidl_error() {
316 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
317 let http_loader = HttpFidlResponder::new(|| Err(FidlError::InvalidHeader));
318 assert_eq!(
319 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
320 Err(ResolverError::Internal)
321 );
322 }
323
324 #[fuchsia::test(allow_stalls = false)]
325 async fn returns_lookup_error_when_response_has_no_body() {
326 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
327 let http_loader = HttpFidlResponder::new(|| {
328 Ok(HttpResponse {
329 error: None,
330 body: None,
331 final_url: None,
332 status_code: None,
333 status_line: None,
334 headers: None,
335 redirect: None,
336 ..Default::default()
337 })
338 });
339 assert_eq!(
340 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
341 Err(ResolverError::Lookup)
342 );
343 }
344
345 #[fuchsia::test(allow_stalls = false)]
346 async fn returns_lookup_error_when_body_is_unreadable() {
347 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
348 let http_loader = HttpFidlResponder::new(|| {
349 Ok(HttpResponse {
350 error: None,
351 body: Some(zx::Socket::from(zx::NullableHandle::invalid())),
352 final_url: None,
353 status_code: None,
354 status_line: None,
355 headers: None,
356 redirect: None,
357 ..Default::default()
358 })
359 });
360 assert_eq!(
361 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
362 Err(ResolverError::Lookup)
363 );
364 }
365
366 #[fuchsia::test(allow_stalls = false)]
367 async fn returns_lookup_error_when_body_is_not_valid_json() {
368 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
369 let http_loader = HttpByteResponder::new(b"hello world".to_vec());
370 assert_eq!(
371 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
372 Err(ResolverError::Lookup)
373 );
374 }
375
376 #[fuchsia::test(allow_stalls = false)]
377 async fn returns_lookup_error_when_body_is_not_a_dictionary() {
378 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
379 let http_loader = HttpByteResponder::new(b"42".to_vec());
380 assert_eq!(
381 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
382 Err(ResolverError::Lookup)
383 );
384 }
385
386 #[fuchsia::test(allow_stalls = false)]
387 async fn returns_lookup_error_when_body_is_missing_location() {
388 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
389 let http_loader = HttpByteResponder::new(b"{}".to_vec());
390 assert_eq!(
391 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
392 Err(ResolverError::Lookup)
393 );
394 }
395
396 #[fuchsia::test(allow_stalls = false)]
397 async fn returns_lookup_error_when_body_is_missing_latitude() {
398 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
399 let http_loader = HttpByteResponder::new(
400 br#"{
401 "location": {
402 "lng": -0.1
403 },
404 "accuracy": 1200.4
405 }"#
406 .to_vec(),
407 );
408 assert_eq!(
409 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
410 Err(ResolverError::Lookup)
411 );
412 }
413
414 #[fuchsia::test(allow_stalls = false)]
415 async fn returns_lookup_error_when_body_is_missing_longitude() {
416 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
417 let http_loader = HttpByteResponder::new(
418 br#"{
419 "location": {
420 "lat": 51.0,
421 },
422 "accuracy": 1200.4
423 }"#
424 .to_vec(),
425 );
426 assert_eq!(
427 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
428 Err(ResolverError::Lookup)
429 );
430 }
431
432 #[fuchsia::test(allow_stalls = false)]
433 async fn returns_lookup_error_when_latitude_is_too_high() {
434 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
435 let http_loader = HttpByteResponder::new(
436 br#"{
437 "location": {
438 "lat": 90.1,
439 "lng": 0.0
440 }
441 }"#
442 .to_vec(),
443 );
444 assert_eq!(
445 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
446 Err(ResolverError::Lookup)
447 );
448 }
449
450 #[fuchsia::test(allow_stalls = false)]
451 async fn returns_lookup_error_when_latitude_is_too_low() {
452 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
453 let http_loader = HttpByteResponder::new(
454 br#"{
455 "location": {
456 "lat": -90.1,
457 "lng": 0.0
458 }
459 }"#
460 .to_vec(),
461 );
462 assert_eq!(
463 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
464 Err(ResolverError::Lookup)
465 );
466 }
467
468 #[fuchsia::test(allow_stalls = false)]
469 async fn returns_lookup_error_when_longitude_is_too_high() {
470 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
471 let http_loader = HttpByteResponder::new(
472 br#"{
473 "location": {
474 "lat": 0.0,
475 "lng": 180.1
476 }
477 }"#
478 .to_vec(),
479 );
480 assert_eq!(
481 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
482 Err(ResolverError::Lookup)
483 );
484 }
485
486 #[fuchsia::test(allow_stalls = false)]
487 async fn returns_lookup_error_when_longitude_is_too_low() {
488 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
489 let http_loader = HttpByteResponder::new(
490 br#"{
491 "location": {
492 "lat": 0.0,
493 "lng": -180.1
494 }
495 }"#
496 .to_vec(),
497 );
498 assert_eq!(
499 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
500 Err(ResolverError::Lookup)
501 );
502 }
503 }
504
505 mod response_success_reporting {
506 use super::super::test_doubles::HttpByteResponder;
507 use super::super::*;
508 use assert_matches::assert_matches;
509
510 #[fuchsia::test(allow_stalls = false)]
511 async fn returns_success_when_all_fields_are_present() {
512 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
513 let http_loader = HttpByteResponder::new(
514 br#"{
515 "location": {
516 "lat": 51.0,
517 "lng": -0.1
518 },
519 "accuracy": 1200.4
520 }"#
521 .to_vec(),
522 );
523 assert_matches!(
524 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
525 Ok(_)
526 );
527 }
528
529 #[fuchsia::test(allow_stalls = false)]
530 async fn returns_success_when_all_fields_except_accuracy_are_present() {
531 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
532 let http_loader = HttpByteResponder::new(
533 br#"{
534 "location": {
535 "lat": 51.0,
536 "lng": -0.1
537 }
538 }"#
539 .to_vec(),
540 );
541 assert_matches!(
542 RealBssResolver::new(http_loader, "fake_key").resolve(bsses).await,
543 Ok(_)
544 );
545 }
546 }
547
548 mod response_success_contents {
549 use super::super::test_doubles::HttpByteResponder;
550 use super::super::*;
551 use assert_matches::assert_matches;
552
553 #[fuchsia::test(allow_stalls = false)]
554 async fn provides_precise_latitude() {
555 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
556 let http_loader = HttpByteResponder::new(
557 br#"{
558 "location": {
559 "lat": 89.0,
560 "lng": 0
561 }
562 }"#
563 .to_vec(),
564 );
565 assert_matches!(
566 RealBssResolver::new(http_loader, "fake_key")
567 .resolve(bsses)
568 .await.expect("position is none").latitude,
569 latitude if (88.999_999_9..89.000_000_1).contains(&latitude)
573 );
574 }
575
576 #[fuchsia::test(allow_stalls = false)]
577 async fn provides_precise_longitude() {
578 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
579 let http_loader = HttpByteResponder::new(
580 br#"{
581 "location": {
582 "lat": 0,
583 "lng": 179.0
584 }
585 }"#
586 .to_vec(),
587 );
588 assert_matches!(
589 RealBssResolver::new(http_loader, "fake_key")
590 .resolve(bsses)
591 .await.expect("no position").longitude,
592 longitude if (178.999_999_9..179.099_999_9).contains(&longitude)
596 );
597 }
598
599 #[fuchsia::test(allow_stalls = false)]
600 async fn provides_precise_accuracy_when_present() {
601 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
602 let http_loader = HttpByteResponder::new(
603 br#"{
604 "location": {
605 "lat": 51.0,
606 "lng": -0.1
607 },
608 "accuracy": 1200.4
609 }"#
610 .to_vec(),
611 );
612 assert_matches!(
613 RealBssResolver::new(http_loader, "fake_key")
614 .resolve(bsses)
615 .await.expect("no position").extras.accuracy_meters.expect("accuracy is none"),
616 accuracy if (1200.39..1200.41).contains(&accuracy)
617 );
618 }
619
620 #[fuchsia::test(allow_stalls = false)]
621 async fn does_not_fabricate_accuracy() {
622 let bsses = vec![([0, 0, 0, 0, 0, 0], Bss { rssi: None, frequency: None })];
623 let http_loader = HttpByteResponder::new(
624 br#"{
625 "location": {
626 "lat": 51.0,
627 "lng": -0.1
628 }
629 }"#
630 .to_vec(),
631 );
632 assert_eq!(
633 RealBssResolver::new(http_loader, "fake_key")
634 .resolve(bsses)
635 .await
636 .expect("no position")
637 .extras
638 .accuracy_meters,
639 None
640 );
641 }
642 }
643}
644
645#[cfg(test)]
646mod test_doubles {
647 use super::*;
648 use fidl::endpoints::ClientEnd;
649 use fidl_fuchsia_net_http::LoaderClientMarker;
650 use fuchsia_sync::RwLock;
651 use futures::future::{Ready, ready};
652
653 const HTTP_OK: u32 = 200;
654
655 type FetchResponse = Result<HttpResponse, FidlError>;
656
657 pub(super) struct HttpRequestValidator<F>
661 where
662 F: FnMut(HttpRequest) + Send + Sync,
663 {
664 validate: RwLock<F>,
668 }
669
670 pub(super) struct HttpFidlResponder<F>
674 where
675 F: Fn() -> FetchResponse + Send + Sync,
676 {
677 fetch: F,
678 }
679
680 pub(super) struct HttpByteResponder {
684 response: Vec<u8>,
685 }
686
687 impl<F> HttpRequestValidator<F>
688 where
689 F: FnMut(HttpRequest) + Send + Sync,
690 {
691 pub(super) fn new(validate: F) -> Self {
692 Self { validate: RwLock::new(validate) }
693 }
694 }
695
696 impl<F> LoaderProxyInterface for HttpRequestValidator<F>
697 where
698 F: FnMut(HttpRequest) + Send + Sync,
699 {
700 type FetchResponseFut = Ready<Result<HttpResponse, FidlError>>;
701
702 fn fetch(&self, request: HttpRequest) -> Self::FetchResponseFut {
703 let validate = &mut *self.validate.write();
705 let final_url = request.url.clone();
706 validate(request);
707 ready(Ok(HttpResponse {
708 error: None,
709 body: None,
710 final_url,
711 status_code: Some(HTTP_OK),
712 status_line: None,
713 headers: None,
714 redirect: None,
715 ..Default::default()
716 }))
717 }
718
719 fn start(
720 &self,
721 _request: HttpRequest,
722 _client: ClientEnd<LoaderClientMarker>,
723 ) -> Result<(), FidlError> {
724 panic!("internal error: this fake does not implement `start()`");
725 }
726 }
727
728 impl<F> HttpFidlResponder<F>
729 where
730 F: Fn() -> FetchResponse + Send + Sync,
731 {
732 pub(super) fn new(fetch: F) -> Self {
733 Self { fetch }
734 }
735 }
736
737 impl<F> LoaderProxyInterface for HttpFidlResponder<F>
738 where
739 F: Fn() -> FetchResponse + Send + Sync,
740 {
741 type FetchResponseFut = Ready<FetchResponse>;
742
743 fn fetch(&self, _request: HttpRequest) -> Self::FetchResponseFut {
744 ready((self.fetch)())
745 }
746
747 fn start(
748 &self,
749 _request: HttpRequest,
750 _client: ClientEnd<LoaderClientMarker>,
751 ) -> Result<(), FidlError> {
752 panic!("internal error: this stub does not implement `start()`");
753 }
754 }
755
756 impl HttpByteResponder {
757 pub(super) fn new(response: Vec<u8>) -> Self {
758 Self { response }
759 }
760 }
761
762 impl LoaderProxyInterface for HttpByteResponder {
763 type FetchResponseFut = Ready<FetchResponse>;
764
765 fn fetch(&self, _request: HttpRequest) -> Self::FetchResponseFut {
766 let (local_socket, remote_socket) = zx::Socket::create_stream();
767 local_socket.write(&self.response).expect("internal error");
768 ready(Ok(HttpResponse {
769 error: None,
770 body: Some(remote_socket),
771 final_url: None,
772 status_code: Some(HTTP_OK),
773 status_line: None,
774 headers: None,
775 redirect: None,
776 ..Default::default()
777 }))
778 }
779
780 fn start(
781 &self,
782 _request: HttpRequest,
783 _client: ClientEnd<LoaderClientMarker>,
784 ) -> Result<(), FidlError> {
785 panic!("internal error: this stub does not implement `start()`");
786 }
787 }
788}