use serde::Deserialize;
use std::{borrow::Cow, collections::BTreeSet, fmt};
use web_time::{SystemTime, UNIX_EPOCH};
use crate::{
IdeviceError, ReadWrite, RemoteXpcClient, obf,
services::core_device::CoreDeviceError,
xpc::{Dictionary, XPCObject},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ButtonState {
Down,
Up,
}
impl ButtonState {
pub fn raw(self) -> u64 {
match self {
ButtonState::Down => 1,
ButtonState::Up => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerEventType {
Start,
Position,
End,
}
impl DigitizerEventType {
pub fn raw(self) -> u64 {
match self {
DigitizerEventType::Start => 0,
DigitizerEventType::Position => 1,
DigitizerEventType::End => 2,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerEdge {
None,
Top,
Left,
Bottom,
Right,
}
impl DigitizerEdge {
pub fn raw(self) -> u64 {
match self {
DigitizerEdge::None => 0,
DigitizerEdge::Top => 1,
DigitizerEdge::Left => 2,
DigitizerEdge::Bottom => 3,
DigitizerEdge::Right => 4,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DigitizerTarget {
MainScreen,
Display(u64),
}
impl DigitizerTarget {
pub fn raw(self) -> u64 {
match self {
DigitizerTarget::MainScreen => 0,
DigitizerTarget::Display(n) => n,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ScrollTarget {
DigitalCrown,
Dial,
}
impl ScrollTarget {
pub fn raw(self) -> u64 {
match self {
ScrollTarget::DigitalCrown => 0,
ScrollTarget::Dial => 1,
}
}
}
pub mod scroll_phase {
pub const UNDEFINED: u64 = 0x0;
pub const BEGAN: u64 = 0x1;
pub const CHANGED: u64 = 0x2;
pub const ENDED: u64 = 0x4;
pub const CANCELLED: u64 = 0x8;
pub const MAY_BEGIN: u64 = 0x80;
}
pub mod scroll_momentum {
pub const UNDEFINED: u64 = 0x0;
pub const CONTINUE: u64 = 0x1;
pub const START: u64 = 0x2;
pub const END: u64 = 0x4;
pub const WILL_BEGIN: u64 = 0x8;
pub const INTERRUPTED: u64 = 0x10;
}
pub const DIGITIZER_REPORT_ID: u8 = 0x13;
pub const TOUCHSCREEN_REPORT_ID: u8 = 0x09;
pub const TOUCHSCREEN_STATE_CONTACT: u8 = 0xC2;
pub const TOUCHSCREEN_STATE_RELEASE: u8 = 0x02;
pub const TOUCHSCREEN_CONTACT_COUNT_MAXIMUM: u8 = 5;
pub const DIGITIZER_SURFACE_MAIN_TOUCHSCREEN: u64 = 257;
pub const DIGITIZER_SURFACE_TOUCHSCREEN_GESTURE: u64 = 1281;
const TOUCHSCREEN_REPORT_SIZE: usize = 58;
const TOUCHSCREEN_CONTACTS_OFFSET: usize = 3;
const TOUCHSCREEN_CONTACT_SIZE: usize = 5;
const MAIN_KEYBOARD_REPORT_ID: u8 = 0x01;
const MAIN_KEYBOARD_USAGE_BITMAP_BYTES: usize = 29;
const MAIN_KEYBOARD_REPORT_SIZE: usize = 39;
const MAIN_KEYBOARD_MIN_USAGE: u16 = 0x01;
const MAIN_KEYBOARD_MAX_USAGE: u16 = 0xE7;
const MAIN_KEYBOARD_PRIMARY_USAGE: u64 = 0x06;
const MAIN_KEYBOARD_PRIMARY_USAGE_PAGE: u64 = 0x01;
const MAIN_KEYBOARD_VENDOR_ID: i64 = 0x05AC;
const MAIN_KEYBOARD_PRODUCT_ID: i64 = 0x0250;
const MAIN_KEYBOARD_REQUESTED_SERVICE_ID: u64 = 0x1_0000_2001;
const MAIN_KEYBOARD_PRODUCT: &str = "idevice mainKeyboard";
const MAIN_KEYBOARD_MANUFACTURER: &str = "idevice";
const MAX_CONNECTED_HID_SERVICES: usize = 256;
const MAX_HID_PRODUCT_BYTES: usize = 256;
const MAIN_KEYBOARD_REPORT_DESCRIPTOR: [u8; 56] = [
0x85, 0x01, 0x05, 0x07, 0x19, 0x01, 0x29, 0xE7, 0x96, 0xE8, 0x00, 0x75, 0x01, 0x15, 0x01, 0x26,
0xE7, 0x00, 0x81, 0x02, 0xA1, 0x02, 0x06, 0x1A, 0xFF, 0x0A, 0xF1, 0xE0, 0x19, 0x00, 0x29, 0x00,
0x75, 0x08, 0x95, 0x01, 0x81, 0x01, 0xC0, 0x06, 0x00, 0xFF, 0x0A, 0x02, 0x01, 0x15, 0x00, 0x26,
0xFF, 0x00, 0x75, 0x08, 0x95, 0x08, 0x81, 0x02,
];
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct KeyboardUsage(u8);
impl KeyboardUsage {
pub fn new(raw: u16) -> Result<Self, MainKeyboardError> {
if !(MAIN_KEYBOARD_MIN_USAGE..=MAIN_KEYBOARD_MAX_USAGE).contains(&raw) {
return Err(MainKeyboardError::InvalidUsage);
}
Ok(Self(raw as u8))
}
pub const fn raw(self) -> u8 {
self.0
}
const fn bitmap_position(self) -> (usize, u8) {
let usage = self.0 as usize;
(usage / 8, 1 << (usage % 8))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum MainKeyboardError {
#[error("mainKeyboard transport is unavailable")]
Transport,
#[error("mainKeyboard response is malformed")]
MalformedResponse,
#[error("mainKeyboard service was not visible after creation")]
ServiceNotVisible,
#[error("mainKeyboard service identity was ambiguous")]
AmbiguousIdentity,
#[error("mainKeyboard metadata evidence was missing")]
MetadataMissing,
#[error("mainKeyboard metadata evidence was invalid")]
MetadataInvalid,
#[error("mainKeyboard usage identity did not match")]
UsageMismatch,
#[error("mainKeyboard codable identity evidence was missing")]
IdentityEvidenceMissing,
#[error("mainKeyboard virtual-service identity did not match")]
VirtualServiceMismatch,
#[error("mainKeyboard report descriptor identity did not match")]
DescriptorMismatch,
#[error("mainKeyboard constructor rollback failed")]
RollbackFailed,
#[error("mainKeyboard service is no longer active")]
Inactive,
#[error("mainKeyboard usage is outside the supported report range")]
InvalidUsage,
#[error("mainKeyboard key is already pressed")]
KeyAlreadyPressed,
#[error("mainKeyboard key is not pressed")]
KeyNotPressed,
#[error("mainKeyboard service remained registered after removal")]
StillRegistered,
}
struct MainKeyboardOwnership;
pub struct MainKeyboardService {
service_id: u64,
pressed: [u8; MAIN_KEYBOARD_USAGE_BITMAP_BYTES],
active: bool,
_ownership: MainKeyboardOwnership,
}
impl MainKeyboardService {
pub const fn is_active(&self) -> bool {
self.active
}
pub fn pressed_count(&self) -> usize {
self.pressed
.iter()
.map(|byte| byte.count_ones() as usize)
.sum()
}
}
impl fmt::Debug for MainKeyboardService {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("MainKeyboardService")
.field("service_id", &"<redacted>")
.field("pressed_count", &self.pressed_count())
.field("active", &self.active)
.finish()
}
}
fn universal_hid_request(payload: XPCObject) -> XPCObject {
let universal_hid_feature: Cow<'static, str> =
obf!("com.apple.coredevice.feature.remote.universalhidservice");
crate::xpc!({
"featureIdentifier": universal_hid_feature.to_string(),
"messageType": "Request",
"payload": payload,
})
}
fn build_connected_services_request() -> XPCObject {
let payload = crate::xpc!({
"connectedServices": XPCObject::Dictionary(Dictionary::new()),
});
universal_hid_request(payload)
}
fn build_main_keyboard_create_request() -> XPCObject {
let payload = crate::xpc!({
"createService": {
"_0": {
"DeviceUsagePairs": [{
"DeviceUsage": MAIN_KEYBOARD_PRIMARY_USAGE as i64,
"DeviceUsagePage": MAIN_KEYBOARD_PRIMARY_USAGE_PAGE as i64,
}],
"PrimaryUsage": MAIN_KEYBOARD_PRIMARY_USAGE,
"PrimaryUsagePage": MAIN_KEYBOARD_PRIMARY_USAGE_PAGE,
"Product": MAIN_KEYBOARD_PRODUCT,
"ProductID": MAIN_KEYBOARD_PRODUCT_ID,
"VendorID": MAIN_KEYBOARD_VENDOR_ID,
"_CoreDevice_codablePropertyStorage": {
"Manufacturer": { "string": MAIN_KEYBOARD_MANUFACTURER },
"Product": { "string": MAIN_KEYBOARD_PRODUCT },
"ProductID": { "int": MAIN_KEYBOARD_PRODUCT_ID },
"VendorID": { "int": MAIN_KEYBOARD_VENDOR_ID },
"PrimaryUsage": { "int": MAIN_KEYBOARD_PRIMARY_USAGE as i64 },
"PrimaryUsagePage": {
"int": MAIN_KEYBOARD_PRIMARY_USAGE_PAGE as i64,
},
"DeviceUsagePairs": {
"array": [{
"dictionary": {
"DeviceUsage": {
"int": MAIN_KEYBOARD_PRIMARY_USAGE as i64,
},
"DeviceUsagePage": {
"int": MAIN_KEYBOARD_PRIMARY_USAGE_PAGE as i64,
},
},
}],
},
"Transport": { "string": "USB" },
"ReportDescriptor": {
"data": MAIN_KEYBOARD_REPORT_DESCRIPTOR.to_vec(),
},
"UniversalControlVirtualService": { "bool": true },
"_ServiceID": { "uint": MAIN_KEYBOARD_REQUESTED_SERVICE_ID },
},
"_ServiceID": MAIN_KEYBOARD_REQUESTED_SERVICE_ID,
},
},
});
universal_hid_request(payload)
}
fn build_main_keyboard_service_request(operation: &'static str, service_id: u64) -> XPCObject {
let payload = crate::xpc!({
operation: {
"_0": service_id,
}
});
universal_hid_request(payload)
}
fn main_keyboard_usage_bitmap(
usages: impl IntoIterator<Item = KeyboardUsage>,
) -> [u8; MAIN_KEYBOARD_USAGE_BITMAP_BYTES] {
let mut bitmap = [0; MAIN_KEYBOARD_USAGE_BITMAP_BYTES];
for usage in usages {
let (byte, mask) = usage.bitmap_position();
bitmap[byte] |= mask;
}
bitmap
}
fn build_main_keyboard_report(bitmap: &[u8; MAIN_KEYBOARD_USAGE_BITMAP_BYTES]) -> Vec<u8> {
let timestamp = default_timestamp();
let mut report = Vec::with_capacity(MAIN_KEYBOARD_REPORT_SIZE);
report.push(MAIN_KEYBOARD_REPORT_ID);
report.extend_from_slice(bitmap);
report.push(0);
report.extend_from_slice(×tamp.to_le_bytes()[..6]);
report.extend_from_slice(&[0, 0]);
debug_assert_eq!(report.len(), MAIN_KEYBOARD_REPORT_SIZE);
report
}
fn build_send_report_request(service_id: u64, report: Vec<u8>) -> XPCObject {
let payload = crate::xpc!({
"send": {
"_0": report,
"_1": service_id,
}
});
universal_hid_request(payload)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TouchscreenContact {
pub identity: u8,
pub touching: bool,
pub x: u16,
pub y: u16,
}
fn default_timestamp() -> u64 {
let nanos = SystemTime::now()
.duration_since(UNIX_EPOCH)
.map(|d| d.as_nanos() as u64)
.unwrap_or(0);
nanos & ((1u64 << 48) - 1)
}
pub fn build_digitizer_report(x: i32, y: i32, timestamp: Option<u64>) -> Vec<u8> {
let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
let mut r = Vec::with_capacity(19);
r.push(DIGITIZER_REPORT_ID);
r.extend_from_slice(&x.to_le_bytes());
r.extend_from_slice(&y.to_le_bytes());
r.extend_from_slice(&[0, 0]);
r.extend_from_slice(&ts.to_le_bytes()[..6]);
r.extend_from_slice(&[0, 0]);
r
}
pub fn build_touchscreen_report(state: u8, x: u16, y: u16, timestamp: Option<u64>) -> Vec<u8> {
let mut r = new_touchscreen_report(1, timestamp);
write_touchscreen_contact(&mut r, 0, state, x, y);
r
}
pub fn build_multitouch_report(
contacts: &[TouchscreenContact],
timestamp: Option<u64>,
) -> Result<Vec<u8>, CoreDeviceError> {
if contacts.len() > TOUCHSCREEN_CONTACT_COUNT_MAXIMUM as usize {
return Err(CoreDeviceError::TooManyTouchscreenContacts(contacts.len()));
}
for (index, contact) in contacts.iter().enumerate() {
if contact.identity >= TOUCHSCREEN_CONTACT_COUNT_MAXIMUM {
return Err(CoreDeviceError::InvalidTouchscreenContactIdentity(
contact.identity,
));
}
if contacts[..index]
.iter()
.any(|other| other.identity == contact.identity)
{
return Err(CoreDeviceError::DuplicateTouchscreenContactIdentity(
contact.identity,
));
}
}
let mut r = new_touchscreen_report(contacts.len() as u8, timestamp);
for (slot, contact) in contacts.iter().enumerate() {
let state = (if contact.touching { 0xC0 } else { 0 }) | contact.identity;
write_touchscreen_contact(&mut r, slot, state, contact.x, contact.y);
}
Ok(r)
}
fn multitap_contacts(positions: &[(u16, u16)]) -> Result<Vec<TouchscreenContact>, CoreDeviceError> {
if positions.is_empty() {
return Err(CoreDeviceError::NoTouchscreenContacts);
}
if positions.len() > TOUCHSCREEN_CONTACT_COUNT_MAXIMUM as usize {
return Err(CoreDeviceError::TooManyTouchscreenContacts(positions.len()));
}
Ok(positions
.iter()
.enumerate()
.map(|(identity, &(x, y))| TouchscreenContact {
identity: identity as u8,
touching: true,
x,
y,
})
.collect())
}
fn new_touchscreen_report(contact_count: u8, timestamp: Option<u64>) -> Vec<u8> {
let ts = timestamp.unwrap_or_else(default_timestamp) & ((1u64 << 48) - 1);
let mut r = vec![0; TOUCHSCREEN_REPORT_SIZE];
r[0] = TOUCHSCREEN_REPORT_ID;
r[1] = contact_count;
r[2] = TOUCHSCREEN_CONTACT_COUNT_MAXIMUM;
r[40..44].copy_from_slice(&[0x02, 0x00, 0x00, 0x00]);
r[44..50].copy_from_slice(&ts.to_le_bytes()[..6]);
r
}
fn write_touchscreen_contact(report: &mut [u8], slot: usize, state: u8, x: u16, y: u16) {
let offset = TOUCHSCREEN_CONTACTS_OFFSET + slot * TOUCHSCREEN_CONTACT_SIZE;
report[offset] = state;
report[offset + 1..offset + 3].copy_from_slice(&x.to_le_bytes());
report[offset + 3..offset + 5].copy_from_slice(&y.to_le_bytes());
}
#[derive(Debug)]
pub struct IndigoHidClient<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}
#[cfg(feature = "rsd")]
impl crate::RsdService for IndigoHidClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> Cow<'static, str> {
obf!("com.apple.coredevice.hid.indigo")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
let mut inner = RemoteXpcClient::new(stream).await?;
inner.do_handshake().await?;
Ok(Self { inner })
}
}
impl<R: ReadWrite> IndigoHidClient<R> {
pub fn new(inner: RemoteXpcClient<R>) -> Self {
Self { inner }
}
async fn send_event(
&mut self,
message_type: &str,
feature_identifier: Cow<'static, str>,
payload: Dictionary,
) -> Result<(), IdeviceError> {
let mut msg = Dictionary::new();
msg.insert(
"messageType".into(),
XPCObject::String(message_type.to_string()),
);
msg.insert("payload".into(), XPCObject::Dictionary(payload));
msg.insert(
"featureIdentifier".into(),
XPCObject::String(feature_identifier.into()),
);
self.inner.send_object(msg, false).await
}
pub async fn send_button(
&mut self,
usage_page: u64,
usage_code: u64,
state: ButtonState,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("state".into(), XPCObject::UInt64(state.raw()));
payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
self.send_event(
"IndigoButtonEvent",
obf!("com.apple.coredevice.feature.remote.hid.button"),
payload,
)
.await
}
pub async fn send_keyboard(
&mut self,
usage_code: u64,
state: ButtonState,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("usageCode".into(), XPCObject::UInt64(usage_code));
payload.insert("state".into(), XPCObject::UInt64(state.raw()));
self.send_event(
"IndigoKeyboardButtonEvent",
obf!("com.apple.coredevice.feature.remote.hid.keyboard"),
payload,
)
.await
}
pub async fn send_digitizer(
&mut self,
point_one: (f64, f64),
point_two: Option<(f64, f64)>,
event_type: DigitizerEventType,
edge: DigitizerEdge,
target: DigitizerTarget,
) -> Result<(), IdeviceError> {
fn point(x: f64, y: f64) -> XPCObject {
let mut p = Dictionary::new();
p.insert("x".into(), XPCObject::Double(x));
p.insert("y".into(), XPCObject::Double(y));
XPCObject::Dictionary(p)
}
let mut payload = Dictionary::new();
payload.insert("pointOne".into(), point(point_one.0, point_one.1));
if let Some((x, y)) = point_two {
payload.insert("pointTwo".into(), point(x, y));
}
payload.insert("eventType".into(), XPCObject::UInt64(event_type.raw()));
payload.insert("edge".into(), XPCObject::UInt64(edge.raw()));
payload.insert("target".into(), XPCObject::UInt64(target.raw()));
self.send_event(
"IndigoDigitizerEvent",
obf!("com.apple.coredevice.feature.remote.hid.digitizer"),
payload,
)
.await
}
pub async fn send_scroll(
&mut self,
point: (f64, f64, f64),
phase: u64,
momentum: u64,
target: ScrollTarget,
) -> Result<(), IdeviceError> {
let mut p = Dictionary::new();
p.insert("x".into(), XPCObject::Double(point.0));
p.insert("y".into(), XPCObject::Double(point.1));
p.insert("z".into(), XPCObject::Double(point.2));
let mut payload = Dictionary::new();
payload.insert("point".into(), XPCObject::Dictionary(p));
payload.insert("phase".into(), XPCObject::UInt64(phase));
payload.insert("momentum".into(), XPCObject::UInt64(momentum));
payload.insert("target".into(), XPCObject::UInt64(target.raw()));
self.send_event(
"IndigoScrollEvent",
obf!("com.apple.coredevice.feature.remote.hid.scroll"),
payload,
)
.await
}
pub async fn send_vendor_defined(
&mut self,
usage_page: u64,
usage: u64,
version: u64,
data: Vec<u8>,
) -> Result<(), IdeviceError> {
let mut payload = Dictionary::new();
payload.insert("usagePage".into(), XPCObject::UInt64(usage_page));
payload.insert("usage".into(), XPCObject::UInt64(usage));
payload.insert("version".into(), XPCObject::UInt64(version));
payload.insert("data".into(), XPCObject::Data(data));
self.send_event(
"IndigoVendorDefinedEvent",
obf!("com.apple.coredevice.feature.remote.hid.vendordefined"),
payload,
)
.await
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct HidSurface {
#[serde(rename = "_ServiceID")]
pub service_id: u64,
#[serde(rename = "Product")]
pub product: Option<String>,
#[serde(rename = "PrimaryUsage")]
pub primary_usage: Option<u64>,
#[serde(rename = "PrimaryUsagePage")]
pub primary_usage_page: Option<u64>,
}
fn parse_hid_surfaces(response: &plist::Value) -> Result<Vec<HidSurface>, IdeviceError> {
let services = response
.as_dictionary()
.and_then(|dictionary| dictionary.get("connectedServices"))
.ok_or(CoreDeviceError::MissingField("connectedServices"))?;
let surfaces: Vec<HidSurface> = plist::from_value(services)
.map_err(|_| CoreDeviceError::MalformedField("connectedServices"))?;
if surfaces.len() > MAX_CONNECTED_HID_SERVICES {
return Err(CoreDeviceError::MalformedField("connectedServices").into());
}
let mut identifiers = BTreeSet::new();
for surface in &surfaces {
if surface.service_id == 0 || !identifiers.insert(surface.service_id) {
return Err(CoreDeviceError::MalformedField("connectedServices").into());
}
if surface.product.as_ref().is_some_and(|product| {
product.is_empty()
|| product.len() > MAX_HID_PRODUCT_BYTES
|| product.chars().any(char::is_control)
}) || surface
.primary_usage
.is_some_and(|usage| usage > u64::from(u16::MAX))
|| surface
.primary_usage_page
.is_some_and(|usage_page| usage_page > u64::from(u16::MAX))
{
return Err(CoreDeviceError::MalformedField("connectedServices").into());
}
}
Ok(surfaces)
}
fn parse_created_main_keyboard_id(response: &plist::Value) -> Result<u64, MainKeyboardError> {
response
.as_dictionary()
.and_then(|dictionary| dictionary.get("serviceID"))
.and_then(plist::Value::as_unsigned_integer)
.filter(|service_id| *service_id != 0)
.ok_or(MainKeyboardError::MalformedResponse)
}
fn codable_storage_value<'a>(
storage: &'a plist::Dictionary,
key: &str,
tag: &str,
) -> Option<&'a plist::Value> {
storage
.get(key)
.and_then(plist::Value::as_dictionary)
.and_then(|wrapper| wrapper.get(tag))
}
fn confirm_main_keyboard_identity(
response: &plist::Value,
service_id: u64,
) -> Result<(), MainKeyboardError> {
let encoded = response
.as_dictionary()
.and_then(|dictionary| dictionary.get("connectedServices"))
.and_then(plist::Value::as_array)
.ok_or(MainKeyboardError::MalformedResponse)?;
if encoded.len() > MAX_CONNECTED_HID_SERVICES {
return Err(MainKeyboardError::MalformedResponse);
}
let mut matching = encoded.iter().filter_map(|value| {
let dictionary = value.as_dictionary()?;
(dictionary
.get("_ServiceID")
.and_then(plist::Value::as_unsigned_integer)
== Some(service_id))
.then_some(dictionary)
});
let Some(surface) = matching.next() else {
return Err(MainKeyboardError::ServiceNotVisible);
};
if matching.next().is_some() {
return Err(MainKeyboardError::AmbiguousIdentity);
}
if surface
.get("PrimaryUsagePage")
.and_then(plist::Value::as_unsigned_integer)
!= Some(MAIN_KEYBOARD_PRIMARY_USAGE_PAGE)
|| surface
.get("PrimaryUsage")
.and_then(plist::Value::as_unsigned_integer)
!= Some(MAIN_KEYBOARD_PRIMARY_USAGE)
{
return Err(MainKeyboardError::UsageMismatch);
}
let storage = surface
.get("_CoreDevice_codablePropertyStorage")
.and_then(plist::Value::as_dictionary)
.ok_or(MainKeyboardError::IdentityEvidenceMissing)?;
if codable_storage_value(storage, "_ServiceID", "uint")
.and_then(plist::Value::as_unsigned_integer)
!= Some(service_id)
{
return Err(MainKeyboardError::IdentityEvidenceMissing);
}
if codable_storage_value(storage, "PrimaryUsagePage", "int")
.and_then(plist::Value::as_unsigned_integer)
!= Some(MAIN_KEYBOARD_PRIMARY_USAGE_PAGE)
|| codable_storage_value(storage, "PrimaryUsage", "int")
.and_then(plist::Value::as_unsigned_integer)
!= Some(MAIN_KEYBOARD_PRIMARY_USAGE)
{
return Err(MainKeyboardError::UsageMismatch);
}
if codable_storage_value(storage, "UniversalControlVirtualService", "bool")
.and_then(plist::Value::as_boolean)
!= Some(true)
{
return Err(MainKeyboardError::VirtualServiceMismatch);
}
if codable_storage_value(storage, "ReportDescriptor", "data").and_then(plist::Value::as_data)
!= Some(MAIN_KEYBOARD_REPORT_DESCRIPTOR.as_slice())
{
return Err(MainKeyboardError::DescriptorMismatch);
}
for key in ["Product", "Manufacturer"] {
let value = codable_storage_value(storage, key, "string")
.and_then(plist::Value::as_string)
.ok_or(MainKeyboardError::MetadataMissing)?;
if value.is_empty()
|| value.len() > MAX_HID_PRODUCT_BYTES
|| value.chars().any(char::is_control)
{
return Err(MainKeyboardError::MetadataInvalid);
}
}
Ok(())
}
trait MainKeyboardWire {
async fn request(&mut self, request: XPCObject) -> Result<plist::Value, MainKeyboardError>;
async fn send(&mut self, request: XPCObject) -> Result<(), MainKeyboardError>;
}
#[derive(Debug)]
pub struct UniversalHidServiceClient<R: ReadWrite> {
inner: RemoteXpcClient<R>,
}
#[cfg(feature = "rsd")]
impl crate::RsdService for UniversalHidServiceClient<Box<dyn ReadWrite>> {
fn rsd_service_name() -> Cow<'static, str> {
obf!("com.apple.coredevice.hid.universalhidservice")
}
async fn from_stream(stream: Box<dyn ReadWrite>) -> Result<Self, IdeviceError> {
let mut inner = RemoteXpcClient::new(stream).await?;
inner.do_handshake().await?;
Ok(Self { inner })
}
}
impl<R: ReadWrite> MainKeyboardWire for UniversalHidServiceClient<R> {
async fn request(&mut self, request: XPCObject) -> Result<plist::Value, MainKeyboardError> {
self.inner
.send_object(request, true)
.await
.map_err(|_| MainKeyboardError::Transport)?;
self.inner
.recv()
.await
.map_err(|_| MainKeyboardError::Transport)
}
async fn send(&mut self, request: XPCObject) -> Result<(), MainKeyboardError> {
self.inner
.send_object(request, false)
.await
.map_err(|_| MainKeyboardError::Transport)
}
}
async fn connected_services_response(
wire: &mut impl MainKeyboardWire,
) -> Result<plist::Value, MainKeyboardError> {
wire.request(build_connected_services_request()).await
}
async fn list_hid_surfaces_on_wire(
wire: &mut impl MainKeyboardWire,
) -> Result<Vec<HidSurface>, MainKeyboardError> {
let response = connected_services_response(wire).await?;
parse_hid_surfaces(&response).map_err(|_| MainKeyboardError::MalformedResponse)
}
async fn confirm_main_keyboard_on_wire(
wire: &mut impl MainKeyboardWire,
service_id: u64,
) -> Result<(), MainKeyboardError> {
let response = connected_services_response(wire).await?;
parse_hid_surfaces(&response).map_err(|_| MainKeyboardError::MalformedResponse)?;
confirm_main_keyboard_identity(&response, service_id)
}
async fn create_main_keyboard_on_wire(
wire: &mut impl MainKeyboardWire,
) -> Result<MainKeyboardService, MainKeyboardError> {
let response = wire.request(build_main_keyboard_create_request()).await?;
let service_id = parse_created_main_keyboard_id(&response)?;
let confirmation = confirm_main_keyboard_on_wire(wire, service_id).await;
if let Err(error) = confirmation {
let rollback = wire
.send(build_main_keyboard_service_request(
"removeService",
service_id,
))
.await;
if rollback.is_err() {
return Err(MainKeyboardError::RollbackFailed);
}
return Err(error);
}
Ok(MainKeyboardService {
service_id,
pressed: [0; MAIN_KEYBOARD_USAGE_BITMAP_BYTES],
active: true,
_ownership: MainKeyboardOwnership,
})
}
async fn replace_main_keyboard_usages_on_wire(
wire: &mut impl MainKeyboardWire,
service: &mut MainKeyboardService,
pressed: [u8; MAIN_KEYBOARD_USAGE_BITMAP_BYTES],
) -> Result<(), MainKeyboardError> {
if !service.active {
return Err(MainKeyboardError::Inactive);
}
wire.send(build_send_report_request(
service.service_id,
build_main_keyboard_report(&pressed),
))
.await?;
service.pressed = pressed;
Ok(())
}
async fn reset_main_keyboard_on_wire(
wire: &mut impl MainKeyboardWire,
service: &mut MainKeyboardService,
) -> Result<(), MainKeyboardError> {
if !service.active {
return Ok(());
}
wire.send(build_main_keyboard_service_request(
"resetGestureState",
service.service_id,
))
.await?;
service.pressed.fill(0);
Ok(())
}
async fn remove_main_keyboard_on_wire(
wire: &mut impl MainKeyboardWire,
service: &mut MainKeyboardService,
) -> Result<(), MainKeyboardError> {
if !service.active {
return Ok(());
}
let _reset_result = wire
.send(build_main_keyboard_service_request(
"resetGestureState",
service.service_id,
))
.await;
wire.send(build_main_keyboard_service_request(
"removeService",
service.service_id,
))
.await?;
let surfaces = list_hid_surfaces_on_wire(wire).await?;
if surfaces
.iter()
.any(|surface| surface.service_id == service.service_id)
{
return Err(MainKeyboardError::StillRegistered);
}
service.pressed.fill(0);
service.active = false;
Ok(())
}
impl<R: ReadWrite> UniversalHidServiceClient<R> {
pub fn new(inner: RemoteXpcClient<R>) -> Self {
Self { inner }
}
pub async fn list_connected_services(&mut self) -> Result<Vec<HidSurface>, IdeviceError> {
let msg = build_connected_services_request();
self.inner.send_object(msg, true).await?;
let res = self.inner.recv().await?;
parse_hid_surfaces(&res)
}
pub async fn create_main_keyboard(&mut self) -> Result<MainKeyboardService, MainKeyboardError> {
create_main_keyboard_on_wire(self).await
}
pub async fn set_main_keyboard_usages(
&mut self,
service: &mut MainKeyboardService,
pressed: impl IntoIterator<Item = KeyboardUsage>,
) -> Result<(), MainKeyboardError> {
replace_main_keyboard_usages_on_wire(self, service, main_keyboard_usage_bitmap(pressed))
.await
}
pub async fn main_keyboard_key_down(
&mut self,
service: &mut MainKeyboardService,
usage: KeyboardUsage,
) -> Result<(), MainKeyboardError> {
let mut pressed = service.pressed;
let (byte, mask) = usage.bitmap_position();
if pressed[byte] & mask != 0 {
return Err(MainKeyboardError::KeyAlreadyPressed);
}
pressed[byte] |= mask;
replace_main_keyboard_usages_on_wire(self, service, pressed).await
}
pub async fn main_keyboard_key_up(
&mut self,
service: &mut MainKeyboardService,
usage: KeyboardUsage,
) -> Result<(), MainKeyboardError> {
let mut pressed = service.pressed;
let (byte, mask) = usage.bitmap_position();
if pressed[byte] & mask == 0 {
return Err(MainKeyboardError::KeyNotPressed);
}
pressed[byte] &= !mask;
replace_main_keyboard_usages_on_wire(self, service, pressed).await
}
pub async fn reset_main_keyboard(
&mut self,
service: &mut MainKeyboardService,
) -> Result<(), MainKeyboardError> {
reset_main_keyboard_on_wire(self, service).await
}
pub async fn remove_main_keyboard(
&mut self,
service: &mut MainKeyboardService,
) -> Result<(), MainKeyboardError> {
remove_main_keyboard_on_wire(self, service).await
}
pub async fn send_report(
&mut self,
service_id: u64,
report: Vec<u8>,
) -> Result<(), IdeviceError> {
let msg = build_send_report_request(service_id, report);
self.inner.send_object(msg, false).await
}
pub async fn send_digitizer(
&mut self,
x: i32,
y: i32,
service_id: u64,
timestamp: Option<u64>,
) -> Result<(), IdeviceError> {
self.send_report(service_id, build_digitizer_report(x, y, timestamp))
.await
}
pub async fn send_touchscreen(
&mut self,
state: u8,
x: u16,
y: u16,
timestamp: Option<u64>,
) -> Result<(), IdeviceError> {
self.send_report(
DIGITIZER_SURFACE_MAIN_TOUCHSCREEN,
build_touchscreen_report(state, x, y, timestamp),
)
.await
}
pub async fn send_multitouch(
&mut self,
contacts: &[TouchscreenContact],
timestamp: Option<u64>,
) -> Result<(), IdeviceError> {
let report = build_multitouch_report(contacts, timestamp)?;
self.send_report(DIGITIZER_SURFACE_MAIN_TOUCHSCREEN, report)
.await
}
pub async fn multi_tap(&mut self, positions: &[(u16, u16)]) -> Result<(), IdeviceError> {
let mut contacts = multitap_contacts(positions)?;
self.send_multitouch(&contacts, None).await?;
crate::time::sleep(std::time::Duration::from_millis(50)).await;
contacts
.iter_mut()
.for_each(|contact| contact.touching = false);
self.send_multitouch(&contacts, None).await
}
pub async fn tap(&mut self, x: u16, y: u16) -> Result<(), IdeviceError> {
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
.await?;
crate::time::sleep(std::time::Duration::from_millis(50)).await;
self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x, y, None)
.await
}
pub async fn drag(
&mut self,
x1: u16,
y1: u16,
x2: u16,
y2: u16,
steps: u32,
delay_ms: u64,
) -> Result<(), IdeviceError> {
let steps = steps.max(1);
for i in 0..steps {
let t = i as f64 / steps as f64;
let x = (x1 as f64 + (x2 as f64 - x1 as f64) * t).round() as u16;
let y = (y1 as f64 + (y2 as f64 - y1 as f64) * t).round() as u16;
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x, y, None)
.await?;
if delay_ms > 0 {
crate::time::sleep(std::time::Duration::from_millis(delay_ms)).await;
}
}
self.send_touchscreen(TOUCHSCREEN_STATE_CONTACT, x2, y2, None)
.await?;
self.send_touchscreen(TOUCHSCREEN_STATE_RELEASE, x2, y2, None)
.await
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn digitizer_report_layout() {
let r = build_digitizer_report(100, -50, Some(0x0102030405));
assert_eq!(r.len(), 19);
assert_eq!(r[0], DIGITIZER_REPORT_ID);
assert_eq!(&r[1..5], &100i32.to_le_bytes());
assert_eq!(&r[5..9], &(-50i32).to_le_bytes());
assert_eq!(&r[9..11], &[0, 0]);
assert_eq!(&r[11..17], &0x0102030405u64.to_le_bytes()[..6]);
assert_eq!(&r[17..19], &[0, 0]);
}
#[test]
fn touchscreen_report_layout() {
let r = build_touchscreen_report(TOUCHSCREEN_STATE_CONTACT, 375, 812, Some(0xAABBCCDD));
assert_eq!(r.len(), 58);
assert_eq!(&r[0..4], &[0x09, 0x01, 0x05, 0xC2]);
assert_eq!(&r[4..6], &375u16.to_le_bytes());
assert_eq!(&r[6..8], &812u16.to_le_bytes());
assert_eq!(&r[8..40], &[0u8; 32]);
assert_eq!(&r[40..44], &[0x02, 0x00, 0x00, 0x00]);
assert_eq!(&r[44..50], &0xAABBCCDDu64.to_le_bytes()[..6]);
assert_eq!(&r[50..58], &[0u8; 8]);
}
#[test]
fn timestamp_is_truncated_to_48_bits() {
let r = build_digitizer_report(0, 0, Some(u64::MAX));
assert_eq!(&r[11..17], &[0xFF; 6]);
}
#[test]
fn multitouch_report_layout() {
let r = build_multitouch_report(
&[
TouchscreenContact {
identity: 2,
touching: true,
x: 0x1234,
y: 0x5678,
},
TouchscreenContact {
identity: 3,
touching: false,
x: 0x9ABC,
y: 0xDEF0,
},
],
Some(0x0102030405),
)
.unwrap();
assert_eq!(r.len(), 58);
assert_eq!(&r[..3], &[0x09, 0x02, 0x05]);
assert_eq!(&r[3..8], &[0xC2, 0x34, 0x12, 0x78, 0x56]);
assert_eq!(&r[8..13], &[0x03, 0xBC, 0x9A, 0xF0, 0xDE]);
assert_eq!(&r[13..40], &[0; 27]);
assert_eq!(&r[40..44], &[0x02, 0x00, 0x00, 0x00]);
assert_eq!(&r[44..50], &0x0102030405u64.to_le_bytes()[..6]);
assert_eq!(&r[50..], &[0; 8]);
}
#[test]
fn multitouch_report_accepts_five_contacts() {
let contacts = [0, 1, 2, 3, 4].map(|identity| TouchscreenContact {
identity,
touching: true,
x: identity as u16,
y: identity as u16,
});
let r = build_multitouch_report(&contacts, Some(0)).unwrap();
assert_eq!(&r[..3], &[TOUCHSCREEN_REPORT_ID, 5, 5]);
assert_eq!(r[23], 0xC4);
}
#[test]
fn multitouch_report_accepts_an_empty_frame() {
let r = build_multitouch_report(&[], Some(0)).unwrap();
assert_eq!(&r[..3], &[TOUCHSCREEN_REPORT_ID, 0, 5]);
assert_eq!(&r[3..40], &[0; 37]);
}
#[test]
fn multitouch_report_validates_contacts() {
let contact = TouchscreenContact {
identity: 1,
touching: true,
x: 0,
y: 0,
};
assert!(matches!(
build_multitouch_report(&[contact; 6], None),
Err(CoreDeviceError::TooManyTouchscreenContacts(6))
));
assert!(matches!(
build_multitouch_report(&[contact, contact], None),
Err(CoreDeviceError::DuplicateTouchscreenContactIdentity(1))
));
assert!(matches!(
build_multitouch_report(
&[TouchscreenContact {
identity: 5,
..contact
}],
None
),
Err(CoreDeviceError::InvalidTouchscreenContactIdentity(5))
));
}
#[test]
fn multitouch_tap_validates_positions_before_io() {
assert!(matches!(
multitap_contacts(&[]),
Err(CoreDeviceError::NoTouchscreenContacts)
));
assert!(matches!(
multitap_contacts(&[(0, 0); 6]),
Err(CoreDeviceError::TooManyTouchscreenContacts(6))
));
let contacts = multitap_contacts(&[(10, 20), (30, 40)]).unwrap();
assert_eq!(contacts[0].identity, 0);
assert_eq!(contacts[1].identity, 1);
assert!(contacts.iter().all(|contact| contact.touching));
}
}