summaryrefslogtreecommitdiffstats
path: root/third_party/rust/fxa-client/src/ffi.rs
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/fxa-client/src/ffi.rs')
-rw-r--r--third_party/rust/fxa-client/src/ffi.rs345
1 files changed, 345 insertions, 0 deletions
diff --git a/third_party/rust/fxa-client/src/ffi.rs b/third_party/rust/fxa-client/src/ffi.rs
new file mode 100644
index 0000000000..d5fbc91b29
--- /dev/null
+++ b/third_party/rust/fxa-client/src/ffi.rs
@@ -0,0 +1,345 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+//! This module implement the traits and some types that make the FFI code easier to manage.
+//!
+//! Note that the FxA FFI is older than the other FFIs in application-services, and has (direct,
+//! low-level) bindings that live in the mozilla-mobile/android-components repo. As a result, it's a
+//! bit harder to change (anything breaking the ABI requires careful synchronization of updates
+//! across two repos), and doesn't follow all the same conventions that are followed by the other
+//! FFIs.
+//!
+//! None of this is that bad in practice, but much of it is not ideal.
+
+pub use crate::oauth::{AuthorizationPKCEParams, AuthorizationParameters, MetricsParams};
+use crate::{
+ commands,
+ device::{Capability as DeviceCapability, Device, PushSubscription, Type as DeviceType},
+ msg_types, send_tab, AccessTokenInfo, AccountEvent, Error, ErrorKind, IncomingDeviceCommand,
+ IntrospectInfo, Profile, Result, ScopedKey,
+};
+use ffi_support::{
+ implement_into_ffi_by_delegation, implement_into_ffi_by_protobuf, ErrorCode, ExternError,
+};
+
+pub mod error_codes {
+ // Note: -1 and 0 (panic and success) codes are reserved by the ffi-support library
+
+ /// Catch-all error code used for anything that's not a panic or covered by AUTHENTICATION.
+ pub const OTHER: i32 = 1;
+
+ /// Used by `ErrorKind::NoCachedTokens`, `ErrorKind::NoScopedKey`
+ /// and `ErrorKind::RemoteError`'s where `code == 401`.
+ pub const AUTHENTICATION: i32 = 2;
+
+ /// Code for network errors.
+ pub const NETWORK: i32 = 3;
+}
+
+/// # Safety
+/// data is a raw pointer to the protobuf data
+/// get_buffer will return an error if the length is invalid,
+/// or if the pointer is a null pointer
+pub unsafe fn from_protobuf_ptr<T, F: prost::Message + Default + Into<T>>(
+ data: *const u8,
+ len: i32,
+) -> Result<T> {
+ let buffer = get_buffer(data, len)?;
+ let item: Result<F, _> = prost::Message::decode(buffer);
+ item.map(|inner| inner.into()).map_err(|e| e.into())
+}
+
+fn get_code(err: &Error) -> ErrorCode {
+ match err.kind() {
+ ErrorKind::RemoteError { code: 401, .. }
+ | ErrorKind::NoRefreshToken
+ | ErrorKind::NoScopedKey(_)
+ | ErrorKind::NoCachedToken(_) => {
+ log::warn!("Authentication error: {:?}", err);
+ ErrorCode::new(error_codes::AUTHENTICATION)
+ }
+ ErrorKind::RequestError(_) => {
+ log::warn!("Network error: {:?}", err);
+ ErrorCode::new(error_codes::NETWORK)
+ }
+ _ => {
+ log::warn!("Unexpected error: {:?}", err);
+ ErrorCode::new(error_codes::OTHER)
+ }
+ }
+}
+
+impl From<Error> for ExternError {
+ fn from(err: Error) -> ExternError {
+ ExternError::new_error(get_code(&err), err.to_string())
+ }
+}
+
+impl From<AccessTokenInfo> for msg_types::AccessTokenInfo {
+ fn from(a: AccessTokenInfo) -> Self {
+ msg_types::AccessTokenInfo {
+ scope: a.scope,
+ token: a.token,
+ key: a.key.map(Into::into),
+ expires_at: a.expires_at,
+ }
+ }
+}
+
+impl From<IntrospectInfo> for msg_types::IntrospectInfo {
+ fn from(a: IntrospectInfo) -> Self {
+ msg_types::IntrospectInfo { active: a.active }
+ }
+}
+
+impl From<ScopedKey> for msg_types::ScopedKey {
+ fn from(sk: ScopedKey) -> Self {
+ msg_types::ScopedKey {
+ kty: sk.kty,
+ scope: sk.scope,
+ k: sk.k,
+ kid: sk.kid,
+ }
+ }
+}
+
+impl From<Profile> for msg_types::Profile {
+ fn from(p: Profile) -> Self {
+ Self {
+ avatar: Some(p.avatar),
+ avatar_default: Some(p.avatar_default),
+ display_name: p.display_name,
+ email: Some(p.email),
+ uid: Some(p.uid),
+ }
+ }
+}
+
+fn command_to_capability(command: &str) -> Option<msg_types::device::Capability> {
+ match command {
+ commands::send_tab::COMMAND_NAME => Some(msg_types::device::Capability::SendTab),
+ _ => None,
+ }
+}
+
+impl From<Device> for msg_types::Device {
+ fn from(d: Device) -> Self {
+ let capabilities = d
+ .available_commands
+ .keys()
+ .filter_map(|c| command_to_capability(c).map(|cc| cc as i32))
+ .collect();
+ Self {
+ id: d.common.id,
+ display_name: d.common.display_name,
+ r#type: Into::<msg_types::device::Type>::into(d.common.device_type) as i32,
+ push_subscription: d.common.push_subscription.map(Into::into),
+ push_endpoint_expired: d.common.push_endpoint_expired,
+ is_current_device: d.is_current_device,
+ last_access_time: d.last_access_time,
+ capabilities,
+ }
+ }
+}
+
+impl From<DeviceType> for msg_types::device::Type {
+ fn from(t: DeviceType) -> Self {
+ match t {
+ DeviceType::Desktop => msg_types::device::Type::Desktop,
+ DeviceType::Mobile => msg_types::device::Type::Mobile,
+ DeviceType::Tablet => msg_types::device::Type::Tablet,
+ DeviceType::VR => msg_types::device::Type::Vr,
+ DeviceType::TV => msg_types::device::Type::Tv,
+ DeviceType::Unknown => msg_types::device::Type::Unknown,
+ }
+ }
+}
+
+impl From<msg_types::device::Type> for DeviceType {
+ fn from(t: msg_types::device::Type) -> Self {
+ match t {
+ msg_types::device::Type::Desktop => DeviceType::Desktop,
+ msg_types::device::Type::Mobile => DeviceType::Mobile,
+ msg_types::device::Type::Tablet => DeviceType::Tablet,
+ msg_types::device::Type::Vr => DeviceType::VR,
+ msg_types::device::Type::Tv => DeviceType::TV,
+ msg_types::device::Type::Unknown => DeviceType::Unknown,
+ }
+ }
+}
+
+impl From<PushSubscription> for msg_types::device::PushSubscription {
+ fn from(p: PushSubscription) -> Self {
+ Self {
+ endpoint: p.endpoint,
+ public_key: p.public_key,
+ auth_key: p.auth_key,
+ }
+ }
+}
+
+impl From<AccountEvent> for msg_types::AccountEvent {
+ fn from(e: AccountEvent) -> Self {
+ match e {
+ AccountEvent::IncomingDeviceCommand(command) => Self {
+ r#type: msg_types::account_event::AccountEventType::IncomingDeviceCommand as i32,
+ data: Some(msg_types::account_event::Data::DeviceCommand(
+ (*command).into(),
+ )),
+ },
+ AccountEvent::ProfileUpdated => Self {
+ r#type: msg_types::account_event::AccountEventType::ProfileUpdated as i32,
+ data: None,
+ },
+ AccountEvent::AccountAuthStateChanged => Self {
+ r#type: msg_types::account_event::AccountEventType::AccountAuthStateChanged as i32,
+ data: None,
+ },
+ AccountEvent::AccountDestroyed => Self {
+ r#type: msg_types::account_event::AccountEventType::AccountDestroyed as i32,
+ data: None,
+ },
+ AccountEvent::DeviceConnected { device_name } => Self {
+ r#type: msg_types::account_event::AccountEventType::DeviceConnected as i32,
+ data: Some(msg_types::account_event::Data::DeviceConnectedName(
+ device_name,
+ )),
+ },
+ AccountEvent::DeviceDisconnected {
+ device_id,
+ is_local_device,
+ } => Self {
+ r#type: msg_types::account_event::AccountEventType::DeviceDisconnected as i32,
+ data: Some(msg_types::account_event::Data::DeviceDisconnectedData(
+ msg_types::account_event::DeviceDisconnectedData {
+ device_id,
+ is_local_device,
+ },
+ )),
+ },
+ }
+ }
+}
+
+impl From<IncomingDeviceCommand> for msg_types::IncomingDeviceCommand {
+ fn from(data: IncomingDeviceCommand) -> Self {
+ match data {
+ IncomingDeviceCommand::TabReceived { sender, payload } => Self {
+ r#type: msg_types::incoming_device_command::IncomingDeviceCommandType::TabReceived
+ as i32,
+ data: Some(msg_types::incoming_device_command::Data::TabReceivedData(
+ msg_types::incoming_device_command::SendTabData {
+ from: sender.map(Into::into),
+ entries: payload.entries.into_iter().map(Into::into).collect(),
+ },
+ )),
+ },
+ }
+ }
+}
+
+impl From<send_tab::TabHistoryEntry>
+ for msg_types::incoming_device_command::send_tab_data::TabHistoryEntry
+{
+ fn from(data: send_tab::TabHistoryEntry) -> Self {
+ Self {
+ title: data.title,
+ url: data.url,
+ }
+ }
+}
+
+impl From<msg_types::device::Capability> for DeviceCapability {
+ fn from(cap: msg_types::device::Capability) -> Self {
+ match cap {
+ msg_types::device::Capability::SendTab => DeviceCapability::SendTab,
+ }
+ }
+}
+
+impl DeviceCapability {
+ /// # Safety
+ /// Deref pointer thus unsafe
+ pub unsafe fn from_protobuf_array_ptr(ptr: *const u8, len: i32) -> Result<Vec<Self>> {
+ let buffer = get_buffer(ptr, len)?;
+ let capabilities: Result<msg_types::Capabilities, _> = prost::Message::decode(buffer);
+ Ok(capabilities
+ .map(|cc| cc.to_capabilities_vec())
+ .unwrap_or_else(|_| vec![]))
+ }
+}
+
+impl msg_types::Capabilities {
+ pub fn to_capabilities_vec(&self) -> Vec<DeviceCapability> {
+ self.capability
+ .iter()
+ .map(|c| msg_types::device::Capability::from_i32(*c).unwrap().into())
+ .collect()
+ }
+}
+
+unsafe fn get_buffer<'a>(data: *const u8, len: i32) -> Result<&'a [u8]> {
+ match len {
+ len if len < 0 => Err(ErrorKind::InvalidBufferLength(len).into()),
+ 0 => Ok(&[]),
+ _ => {
+ if data.is_null() {
+ return Err(ErrorKind::NullPointer.into());
+ }
+ Ok(std::slice::from_raw_parts(data, len as usize))
+ }
+ }
+}
+
+impl From<msg_types::AuthorizationParams> for AuthorizationParameters {
+ fn from(proto_params: msg_types::AuthorizationParams) -> Self {
+ Self {
+ client_id: proto_params.client_id,
+ scope: proto_params
+ .scope
+ .split_whitespace()
+ .map(|s| s.to_string())
+ .collect(),
+ state: proto_params.state,
+ access_type: proto_params.access_type,
+ pkce_params: proto_params
+ .pkce_params
+ .map(|pkce_params| pkce_params.into()),
+ keys_jwk: proto_params.keys_jwk,
+ }
+ }
+}
+
+impl From<msg_types::MetricsParams> for MetricsParams {
+ fn from(proto_metrics_params: msg_types::MetricsParams) -> Self {
+ Self {
+ parameters: proto_metrics_params.parameters,
+ }
+ }
+}
+
+impl From<msg_types::AuthorizationPkceParams> for AuthorizationPKCEParams {
+ fn from(proto_key_params: msg_types::AuthorizationPkceParams) -> Self {
+ Self {
+ code_challenge: proto_key_params.code_challenge,
+ code_challenge_method: proto_key_params.code_challenge_method,
+ }
+ }
+}
+
+implement_into_ffi_by_protobuf!(msg_types::Profile);
+implement_into_ffi_by_delegation!(Profile, msg_types::Profile);
+implement_into_ffi_by_protobuf!(msg_types::AccessTokenInfo);
+implement_into_ffi_by_delegation!(AccessTokenInfo, msg_types::AccessTokenInfo);
+implement_into_ffi_by_protobuf!(msg_types::IntrospectInfo);
+implement_into_ffi_by_delegation!(IntrospectInfo, msg_types::IntrospectInfo);
+implement_into_ffi_by_protobuf!(msg_types::Device);
+implement_into_ffi_by_delegation!(Device, msg_types::Device);
+implement_into_ffi_by_protobuf!(msg_types::Devices);
+implement_into_ffi_by_delegation!(AccountEvent, msg_types::AccountEvent);
+implement_into_ffi_by_protobuf!(msg_types::AccountEvent);
+implement_into_ffi_by_protobuf!(msg_types::AccountEvents);
+implement_into_ffi_by_delegation!(IncomingDeviceCommand, msg_types::IncomingDeviceCommand);
+implement_into_ffi_by_protobuf!(msg_types::IncomingDeviceCommand);
+implement_into_ffi_by_protobuf!(msg_types::IncomingDeviceCommands);