diff options
Diffstat (limited to 'third_party/rust/fxa-client/src/ffi.rs')
-rw-r--r-- | third_party/rust/fxa-client/src/ffi.rs | 345 |
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); |