viaduct/backend/
ffi.rs

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/. */
4
5use crate::{backend::Backend, settings::GLOBAL_SETTINGS};
6use crate::{error, msg_types, warn, Error};
7use ffi_support::{ByteBuffer, FfiStr};
8
9ffi_support::implement_into_ffi_by_protobuf!(msg_types::Request);
10
11impl From<crate::Request> for msg_types::Request {
12    fn from(request: crate::Request) -> Self {
13        let 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...
19            method: 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}
28
29macro_rules! backend_error {
30    ($($args:tt)*) => {{
31        let msg = format!($($args)*);
32        error!("{}", msg);
33        Error::BackendError(msg)
34    }};
35}
36
37pub struct FfiBackend;
38impl Backend for FfiBackend {
39    fn send(&self, request: crate::Request) -> Result<crate::Response, Error> {
40        use ffi_support::IntoFfi;
41        use prost::Message;
42        super::note_backend("FFI (trusted)");
43
44        let method = request.method;
45        let fetch = callback_holder::get_callback().ok_or(Error::BackendNotInitialized)?;
46        let proto_req: msg_types::Request = request.into();
47        let buf = proto_req.into_ffi_value();
48        let 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.
51        let response_bytes = response.destroy_into_vec();
52
53        let response: msg_types::Response = match Message::decode(response_bytes.as_slice()) {
54            Ok(v) => v,
55            Err(e) => {
56                panic!(
57                    "Failed to parse protobuf returned from fetch callback! {}",
58                    e
59                );
60            }
61        };
62
63        if let Some(exn) = response.exception_message {
64            return Err(Error::NetworkError(format!("Java error: {:?}", exn)));
65        }
66        let status = response
67            .status
68            .ok_or_else(|| backend_error!("Missing HTTP status"))?;
69
70        if status < 0 || status > i32::from(u16::MAX) {
71            return Err(backend_error!("Illegal HTTP status: {}", status));
72        }
73
74        let mut headers = crate::Headers::with_capacity(response.headers.len());
75        for (name, val) in response.headers {
76            let hname = match crate::HeaderName::new(name) {
77                Ok(name) => name,
78                Err(e) => {
79                    // Ignore headers with invalid names, since nobody can look for them anyway.
80                    warn!("Server sent back invalid header name: '{}'", e);
81                    continue;
82                }
83            };
84            // Not using Header::new since the error it returns is for request headers.
85            headers.insert_header(crate::Header::new_unchecked(hname, val));
86        }
87
88        let 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))?;
94
95        Ok(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}
104
105/// 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;
118
119/// Module that manages get/set of the global fetch callback pointer.
120mod callback_holder {
121    use super::FetchCallback;
122    use crate::error;
123    use std::sync::atomic::{AtomicUsize, Ordering};
124
125    /// Note: We only assign to this once.
126    static CALLBACK_PTR: AtomicUsize = AtomicUsize::new(0);
127
128    // 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.
135    ffi_support::static_assert!(
136        STATIC_ASSERT_USIZE_EQ_FUNC_SIZE,
137        std::mem::size_of::<usize>() == std::mem::size_of::<FetchCallback>()
138    );
139
140    ffi_support::static_assert!(
141        STATIC_ASSERT_USIZE_EQ_OPT_FUNC_SIZE,
142        std::mem::size_of::<usize>() == std::mem::size_of::<Option<FetchCallback>>()
143    );
144
145    /// Get the function pointer to the FetchCallback. Panics if the callback
146    /// has not yet been initialized.
147    pub(super) fn get_callback() -> Option<FetchCallback> {
148        let ptr_value = CALLBACK_PTR.load(Ordering::SeqCst);
149        unsafe { std::mem::transmute::<usize, Option<FetchCallback>>(ptr_value) }
150    }
151
152    /// Set the function pointer to the FetchCallback. Returns false if we did nothing because the callback had already been initialized
153    pub(super) fn set_callback(h: FetchCallback) -> bool {
154        let as_usize = h as usize;
155        match CALLBACK_PTR.compare_exchange(0, as_usize, Ordering::SeqCst, Ordering::SeqCst) {
156            Ok(_) => true,
157            Err(_) => {
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.
162                error!("Bug: Initialized CALLBACK_PTR multiple times");
163                false
164            }
165        }
166    }
167}
168
169/// 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 {
173    let mut error = ffi_support::ExternError::default();
174    let 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}
179
180#[no_mangle]
181pub extern "C" fn viaduct_log_error(s: FfiStr<'_>) {
182    let 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}
186
187#[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}
191
192/// 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() {
199    let mut error = ffi_support::ExternError::default();
200    ffi_support::call_with_output(&mut error, || {
201        let url = url::Url::parse("http://10.0.2.2").unwrap();
202        let mut settings = GLOBAL_SETTINGS.write();
203        settings.addn_allowed_insecure_url = Some(url);
204    });
205    error.consume_and_log_if_error();
206}
207
208ffi_support::define_bytebuffer_destructor!(viaduct_destroy_bytebuffer);