apple_ble/
advertisement.rs

1use std::fmt::Debug;
2use std::net::{Ipv4Addr, Ipv6Addr};
3use std::{collections::BTreeMap, error::Error, time::Duration};
4
5#[cfg(not(feature = "disable_afit"))]
6use async_trait::async_trait;
7use bluer::adv::{Advertisement, AdvertisementHandle, Type};
8use bluer::{Device, Address};
9use futures::executor;
10
11use crate::session::Session;
12use crate::util::set_device_addr;
13
14const APPLE_MAGIC: u16 = 0x4c;
15
16pub trait AdvertisableData: Clone + PartialEq + Debug + Sync {
17    fn octets(&self) -> Vec<u8>;
18}
19
20// If the user opted out of using "async_fn_in_trait", use the crate async-trait instead.
21#[cfg_attr(not(feature = "disable_afit"), async_trait)]
22/// Any kind of advertisement.
23pub trait Advertisable<T: AdvertisableData> {
24    /// Advertisement-specific: validate user supplied data.
25    fn validate_user_data(_user_data: &T) -> Result<(), Box<dyn Error>> {
26        Ok(())
27    }
28    /// Advertisement-specific: assemble user supplied data to advertisement.
29    fn assemble_advertisement(
30        session: &mut Session,
31        user_data: &T,
32    ) -> Result<Advertisement, Box<dyn Error>>;
33    /// Register any advertisement.
34    async fn register(
35        session: &mut Session,
36        user_data: &T,
37    ) -> Result<AdvertisementHandle, Box<dyn Error>> {
38        Self::validate_user_data(user_data)?;
39        let advertisement = Self::assemble_advertisement(session, user_data)?;
40        Ok(session.adapter.advertise(advertisement).await?)
41    }
42}
43pub enum AdvertisementType {
44    AirDrop(AirDropAdvertisementData),
45    AirPlaySource, // Carries no dynamic data.
46    AirPlayTarget(AirPlayTargetAdvertisementData),
47    AirPrint(AirPrintAdvertisementData),
48    FindMy(FindMyAdvertisementData),
49}
50pub fn get_adv_data_from_device(device: Device) -> Option<AdvertisementType> {
51    let binding = executor::block_on(device.manufacturer_data()).ok()??;
52    let manufacturer_data = binding.get(&APPLE_MAGIC)?;
53    match manufacturer_data[0] {
54        0x05 => Some(AdvertisementType::AirDrop(
55            AirDropAdvertisementData::try_from(manufacturer_data.clone()).ok()?,
56        )),
57        0x0a => Some(AdvertisementType::AirPlaySource),
58        0x09 => Some(AdvertisementType::AirPlayTarget(
59            AirPlayTargetAdvertisementData::try_from(manufacturer_data.clone()).ok()?,
60        )),
61        0x03 => Some(AdvertisementType::AirPrint(
62            AirPrintAdvertisementData::try_from(manufacturer_data.clone()).ok()?,
63        )),
64        0x12 => Some(AdvertisementType::FindMy(
65            FindMyAdvertisementData::try_from((device.address(), manufacturer_data.clone())).ok()?,
66        )),
67        _ => None,
68    }
69}
70
71/// Data for an AirDrop advertisement.
72#[derive(Clone, PartialEq, Debug)]
73pub struct AirDropAdvertisementData {
74    pub apple_id: [u8; 2],
75    pub phone: [u8; 2],
76    pub email: [u8; 2],
77}
78impl AdvertisableData for AirDropAdvertisementData {
79    fn octets(&self) -> Vec<u8> {
80        [
81            vec![
82                0x05, // Message type
83                0x12, // Message length
84            ],
85            vec![0; 8], // 8bytes of padding
86            vec![0x01], // AirDrop version
87            self.apple_id.to_vec(),
88            self.phone.to_vec(),
89            self.email.to_vec(),
90            self.email.to_vec(),
91        ]
92        .concat()
93    }
94}
95impl TryFrom<Vec<u8>> for AirDropAdvertisementData {
96    type Error = Box<dyn Error>;
97    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
98        Ok(AirDropAdvertisementData {
99            apple_id: value[11..13].try_into()?,
100            phone: value[13..15].try_into()?,
101            email: value[15..17].try_into()?,
102        })
103    }
104}
105
106/// https://github.com/furiousMAC/continuity/blob/master/messages/airdrop.md
107pub struct AirDropAdvertisement;
108impl Advertisable<AirDropAdvertisementData> for AirDropAdvertisement {
109    fn assemble_advertisement(
110        session: &mut Session,
111        user_data: &AirDropAdvertisementData,
112    ) -> Result<Advertisement, Box<dyn Error>> {
113        Ok(Advertisement {
114            advertisement_type: Type::Broadcast,
115            local_name: Some(session.adapter.name().to_string()),
116            timeout: Some(Duration::from_millis(0)),
117            min_interval: Some(Duration::from_millis(100)),
118            max_interval: Some(Duration::from_millis(200)),
119            manufacturer_data: BTreeMap::from([(APPLE_MAGIC, user_data.octets())]),
120            ..Default::default()
121        })
122    }
123}
124
125/// Data for an AirPlay source message
126#[derive(Clone, PartialEq, Debug)]
127pub struct AirPlaySourceAdvertisementData;
128impl AdvertisableData for AirPlaySourceAdvertisementData {
129    fn octets(&self) -> Vec<u8> {
130        // This is constant.
131        vec![
132            0x0a, // Message type
133            0x01, // Message length
134            0x00,
135        ]
136    }
137}
138impl TryFrom<Vec<u8>> for AirPlaySourceAdvertisementData {
139    type Error = Box<dyn Error>;
140    fn try_from(_value: Vec<u8>) -> Result<Self, Self::Error> {
141        Ok(AirPlaySourceAdvertisementData {})
142    }
143}
144
145/// AirPlay source message https://github.com/furiousMAC/continuity/blob/master/messages/airplay_source.md
146pub struct AirPlaySourceAdvertisement;
147impl Advertisable<AirPlaySourceAdvertisementData> for AirPlaySourceAdvertisement {
148    /// The user_data field can be ignored.
149    fn assemble_advertisement(
150        session: &mut Session,
151        user_data: &AirPlaySourceAdvertisementData,
152    ) -> Result<Advertisement, Box<dyn Error>> {
153        Ok(Advertisement {
154            advertisement_type: Type::Broadcast,
155            local_name: Some(session.adapter.name().to_string()),
156            timeout: Some(Duration::from_millis(0)),
157            min_interval: Some(Duration::from_millis(100)),
158            max_interval: Some(Duration::from_millis(200)),
159            manufacturer_data: BTreeMap::from([(APPLE_MAGIC, user_data.octets())]),
160            ..Default::default()
161        })
162    }
163}
164
165/// Data for an AirPlay target message
166#[derive(Clone, PartialEq, Debug)]
167pub struct AirPlayTargetAdvertisementData {
168    pub ip_address: Ipv4Addr,
169}
170impl AdvertisableData for AirPlayTargetAdvertisementData {
171    fn octets(&self) -> Vec<u8> {
172        let ip_address = self.ip_address.octets();
173        [
174            vec![
175                0x09, // Message type
176                0x06, // Message length
177                0x03, 0x07,
178            ],
179            ip_address.to_vec(),
180        ]
181        .concat()
182    }
183}
184impl TryFrom<Vec<u8>> for AirPlayTargetAdvertisementData {
185    type Error = Box<dyn Error>;
186    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
187        let ip_address: [u8; 4] = value[4..8].try_into()?;
188        Ok(AirPlayTargetAdvertisementData {
189            ip_address: Ipv4Addr::from(ip_address),
190        })
191    }
192}
193
194/// AirPlay target message https://github.com/furiousMAC/continuity/blob/master/messages/airplay_target.md
195pub struct AirPlayTargetAdvertisement;
196impl Advertisable<AirPlayTargetAdvertisementData> for AirPlayTargetAdvertisement {
197    fn assemble_advertisement(
198        session: &mut Session,
199        user_data: &AirPlayTargetAdvertisementData,
200    ) -> Result<Advertisement, Box<dyn Error>> {
201        Ok(Advertisement {
202            advertisement_type: Type::Broadcast,
203            local_name: Some(session.adapter.name().to_string()),
204            timeout: Some(Duration::from_millis(0)),
205            min_interval: Some(Duration::from_millis(100)),
206            max_interval: Some(Duration::from_millis(200)),
207            manufacturer_data: BTreeMap::from([(APPLE_MAGIC, user_data.octets())]),
208            ..Default::default()
209        })
210    }
211}
212
213/// Data for an AirPrint message
214#[derive(Clone, PartialEq, Debug)]
215pub struct AirPrintAdvertisementData {
216    pub port: u16,
217    pub ip_addr: Ipv6Addr,
218    pub power: u8,
219}
220impl AdvertisableData for AirPrintAdvertisementData {
221    fn octets(&self) -> Vec<u8> {
222        let port = self.port.to_be_bytes();
223        let ip_addr = self.ip_addr.octets();
224        [
225            vec![
226                0x03, // Message type
227                0x16, // Message length
228                0x74, // Address type
229                0x07, // Resource path
230                0x6f, // Security type
231            ],
232            port.to_vec(),
233            ip_addr.to_vec(),
234            vec![self.power],
235        ]
236        .concat()
237    }
238}
239impl TryFrom<Vec<u8>> for AirPrintAdvertisementData {
240    type Error = Box<dyn Error>;
241    fn try_from(value: Vec<u8>) -> Result<Self, Self::Error> {
242        let ip_address: [u8; 16] = value[7..23].try_into()?;
243        Ok(AirPrintAdvertisementData {
244            port: (value[5] as u16) << 8 | value[6] as u16,
245            ip_addr: Ipv6Addr::from(ip_address),
246            power: value[23],
247        })
248    }
249}
250
251/// AirPrint message https://github.com/furiousMAC/continuity/blob/master/messages/airprint.md
252pub struct AirPrintAdvertisement;
253impl Advertisable<AirPrintAdvertisementData> for AirPrintAdvertisement {
254    fn assemble_advertisement(
255        session: &mut Session,
256        user_data: &AirPrintAdvertisementData,
257    ) -> Result<Advertisement, Box<dyn Error>> {
258        Ok(Advertisement {
259            advertisement_type: Type::Broadcast,
260            local_name: Some(session.adapter.name().to_string()),
261            timeout: Some(Duration::from_millis(0)),
262            min_interval: Some(Duration::from_millis(100)),
263            max_interval: Some(Duration::from_millis(200)),
264            manufacturer_data: BTreeMap::from([(APPLE_MAGIC, user_data.octets())]),
265            ..Default::default()
266        })
267    }
268}
269
270/// Data for a FindMy message
271#[derive(Clone, PartialEq, Debug)]
272pub struct FindMyAdvertisementData {
273    pub public_key: [u8; 28],
274}
275impl AdvertisableData for FindMyAdvertisementData {
276    fn octets(&self) -> Vec<u8> {
277        let public_key = self.public_key.split_at(6);
278        [
279            vec![
280                0x12, // Message type
281                0x19, // Message length
282                0x00,
283            ],
284            public_key.1.to_vec(),
285            vec![public_key.0[0] >> 6],
286        ]
287        .concat()
288    }
289}
290impl TryFrom<(Address, Vec<u8>)> for FindMyAdvertisementData {
291    type Error = Box<dyn Error>;
292    fn try_from(value: (Address, Vec<u8>)) -> Result<Self, Self::Error> {
293        let public_key: [u8; 28] = [&value.0.0, &value.1[2..24]]
294            .concat()
295            .as_slice()
296            .try_into()?;
297        Ok(FindMyAdvertisementData {
298            public_key: public_key,
299        })
300    }
301}
302
303/// FindMy message https://github.com/furiousMAC/continuity/blob/master/messages/findmy.md
304pub struct FindMyAdvertisement;
305impl Advertisable<FindMyAdvertisementData> for FindMyAdvertisement {
306    fn assemble_advertisement(
307        session: &mut Session,
308        user_data: &FindMyAdvertisementData,
309    ) -> Result<Advertisement, Box<dyn Error>> {
310        set_device_addr(session, &user_data.public_key[0..6])?;
311        Ok(Advertisement {
312            advertisement_type: Type::Broadcast,
313            local_name: Some(session.adapter.name().to_string()),
314            timeout: Some(Duration::from_millis(0)),
315            min_interval: Some(Duration::from_millis(100)),
316            max_interval: Some(Duration::from_millis(200)),
317            manufacturer_data: BTreeMap::from([(APPLE_MAGIC, user_data.octets())]),
318            ..Default::default()
319        })
320    }
321}