1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
45use crate::{backend::Backend, settings::GLOBAL_SETTINGS};
6use crate::{error, msg_types, warn, Error};
7use ffi_support::{ByteBuffer, FfiStr};
89ffi_support::implement_into_ffi_by_protobuf!(msg_types::Request);
1011impl From<crate::Request> for msg_types::Request {
12fn from(request: crate::Request) -> Self {
13let settings = GLOBAL_SETTINGS.read();
14 msg_types::Request {
15 url: request.url.to_string(),
16 body: request.body,
17// Real weird that this needs to be specified as an i32, but
18 // it certainly makes it convenient for us...
19method: request.method as i32,
20 headers: request.headers.into(),
21 follow_redirects: settings.follow_redirects,
22 use_caches: settings.use_caches,
23 connect_timeout_secs: settings.connect_timeout.map_or(0, |d| d.as_secs() as i32),
24 read_timeout_secs: settings.read_timeout.map_or(0, |d| d.as_secs() as i32),
25 }
26 }
27}
2829macro_rules! backend_error {
30 ($($args:tt)*) => {{
31let msg = format!($($args)*);
32error!("{}", msg);
33 Error::BackendError(msg)
34 }};
35}
3637pub struct FfiBackend;
38impl Backend for FfiBackend {
39fn send(&self, request: crate::Request) -> Result<crate::Response, Error> {
40use ffi_support::IntoFfi;
41use prost::Message;
42super::note_backend("FFI (trusted)");
4344let method = request.method;
45let fetch = callback_holder::get_callback().ok_or(Error::BackendNotInitialized)?;
46let proto_req: msg_types::Request = request.into();
47let buf = proto_req.into_ffi_value();
48let response = unsafe { fetch(buf) };
49// This way we'll Drop it if we panic, unlike if we just got a slice into
50 // it. Besides, we already own it.
51let response_bytes = response.destroy_into_vec();
5253let response: msg_types::Response = match Message::decode(response_bytes.as_slice()) {
54Ok(v) => v,
55Err(e) => {
56panic!(
57"Failed to parse protobuf returned from fetch callback! {}",
58 e
59 );
60 }
61 };
6263if let Some(exn) = response.exception_message {
64return Err(Error::NetworkError(format!("Java error: {:?}", exn)));
65 }
66let status = response
67 .status
68 .ok_or_else(|| backend_error!("Missing HTTP status"))?;
6970if status < 0 || status > i32::from(u16::MAX) {
71return Err(backend_error!("Illegal HTTP status: {}", status));
72 }
7374let mut headers = crate::Headers::with_capacity(response.headers.len());
75for (name, val) in response.headers {
76let hname = match crate::HeaderName::new(name) {
77Ok(name) => name,
78Err(e) => {
79// Ignore headers with invalid names, since nobody can look for them anyway.
80warn!("Server sent back invalid header name: '{}'", e);
81continue;
82 }
83 };
84// Not using Header::new since the error it returns is for request headers.
85headers.insert_header(crate::Header::new_unchecked(hname, val));
86 }
8788let url = url::Url::parse(
89&response
90 .url
91 .ok_or_else(|| backend_error!("Response has no URL"))?,
92 )
93 .map_err(|e| backend_error!("Response has illegal URL: {}", e))?;
9495Ok(crate::Response {
96 url,
97 request_method: method,
98 body: response.body.unwrap_or_default(),
99 status: status as u16,
100 headers,
101 })
102 }
103}
104105/// Type of the callback we need callers on the other side of the FFI to
106/// provide.
107///
108/// Takes and returns a ffi_support::ByteBuffer. (TODO: it would be nice if we could
109/// make this take/return pointers, so that we could use JNA direct mapping. Maybe
110/// we need some kind of ThinBuffer?)
111///
112/// This is a bit weird, since it requires us to allow code on the other side of
113/// the FFI to allocate a ByteBuffer from us, but it works.
114///
115/// The code on the other side of the FFI is responsible for freeing the ByteBuffer
116/// it's passed using `viaduct_destroy_bytebuffer`.
117type FetchCallback = unsafe extern "C" fn(ByteBuffer) -> ByteBuffer;
118119/// Module that manages get/set of the global fetch callback pointer.
120mod callback_holder {
121use super::FetchCallback;
122use crate::error;
123use std::sync::atomic::{AtomicUsize, Ordering};
124125/// Note: We only assign to this once.
126static CALLBACK_PTR: AtomicUsize = AtomicUsize::new(0);
127128// Overly-paranoid sanity checking to ensure that these types are
129 // convertible between each-other. `transmute` actually should check this for
130 // us too, but this helps document the invariants we rely on in this code.
131 //
132 // Note that these are guaranteed by
133 // https://rust-lang.github.io/unsafe-code-guidelines/layout/function-pointers.html
134 // and thus this is a little paranoid.
135ffi_support::static_assert!(
136 STATIC_ASSERT_USIZE_EQ_FUNC_SIZE,
137 std::mem::size_of::<usize>() == std::mem::size_of::<FetchCallback>()
138 );
139140ffi_support::static_assert!(
141 STATIC_ASSERT_USIZE_EQ_OPT_FUNC_SIZE,
142 std::mem::size_of::<usize>() == std::mem::size_of::<Option<FetchCallback>>()
143 );
144145/// Get the function pointer to the FetchCallback. Panics if the callback
146 /// has not yet been initialized.
147pub(super) fn get_callback() -> Option<FetchCallback> {
148let ptr_value = CALLBACK_PTR.load(Ordering::SeqCst);
149unsafe { std::mem::transmute::<usize, Option<FetchCallback>>(ptr_value) }
150 }
151152/// Set the function pointer to the FetchCallback. Returns false if we did nothing because the callback had already been initialized
153pub(super) fn set_callback(h: FetchCallback) -> bool {
154let as_usize = h as usize;
155match CALLBACK_PTR.compare_exchange(0, as_usize, Ordering::SeqCst, Ordering::SeqCst) {
156Ok(_) => true,
157Err(_) => {
158// This is an internal bug, the other side of the FFI should ensure
159 // it sets this only once. Note that this is actually going to be
160 // before logging is initialized in practice, so there's not a lot
161 // we can actually do here.
162error!("Bug: Initialized CALLBACK_PTR multiple times");
163false
164}
165 }
166 }
167}
168169/// Return a ByteBuffer of the requested size. This is used to store the
170/// response from the callback.
171#[no_mangle]
172pub extern "C" fn viaduct_alloc_bytebuffer(sz: i32) -> ByteBuffer {
173let mut error = ffi_support::ExternError::default();
174let buffer =
175 ffi_support::call_with_output(&mut error, || ByteBuffer::new_with_size(sz.max(0) as usize));
176 error.consume_and_log_if_error();
177 buffer
178}
179180#[no_mangle]
181pub extern "C" fn viaduct_log_error(s: FfiStr<'_>) {
182let mut error = ffi_support::ExternError::default();
183 ffi_support::call_with_output(&mut error, || error!("Viaduct Ffi Error: {}", s.as_str()));
184 error.consume_and_log_if_error();
185}
186187#[no_mangle]
188pub extern "C" fn viaduct_initialize(callback: FetchCallback) -> u8 {
189 ffi_support::abort_on_panic::call_with_output(|| callback_holder::set_callback(callback))
190}
191192/// Allows connections to the hard-coded address the Android Emulator uses for
193/// localhost. It would be easy to support allowing the address to be passed in,
194/// but we've made a decision to avoid that possible footgun. The expectation is
195/// that this will only be called in debug builds or if the app can determine it
196/// is in the emulator, but the Rust code doesn't know that, so we can't check.
197#[no_mangle]
198pub extern "C" fn viaduct_allow_android_emulator_loopback() {
199let mut error = ffi_support::ExternError::default();
200 ffi_support::call_with_output(&mut error, || {
201let url = url::Url::parse("http://10.0.2.2").unwrap();
202let mut settings = GLOBAL_SETTINGS.write();
203 settings.addn_allowed_insecure_url = Some(url);
204 });
205 error.consume_and_log_if_error();
206}
207208ffi_support::define_bytebuffer_destructor!(viaduct_destroy_bytebuffer);