1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
use core::ffi::{c_int, c_void};

use crate::{ble, enums::*, utilities::voidp_to_ref, BLEAdvertisementData, BLEError, BLEServer};
use alloc::boxed::Box;
use once_cell::sync::Lazy;

const BLE_HS_ADV_MAX_SZ: usize = esp_idf_sys::BLE_HS_ADV_MAX_SZ as usize;

// Copied from ble_hs.h, for some reason esp_idf_sys didn't pick this up.
const BLE_HS_FOREVER: i32 = i32::MAX;

pub struct BLEAdvertising {
  adv_params: esp_idf_sys::ble_gap_adv_params,
  scan_response: bool,
  on_complete: Option<Box<dyn FnMut(c_int) + Send + Sync>>,
}

impl BLEAdvertising {
  #[allow(dead_code)]
  pub(crate) fn new() -> Self {
    let mut ret = Self {
      adv_params: esp_idf_sys::ble_gap_adv_params::default(),
      scan_response: true,
      on_complete: None,
    };

    ret.reset().unwrap();
    ret
  }

  pub fn reset(&mut self) -> Result<(), BLEError> {
    if self.is_advertising() {
      self.stop()?;
    }

    self.adv_params.conn_mode = esp_idf_sys::BLE_GAP_CONN_MODE_UND as _;
    self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_GEN as _;
    self.scan_response = true;

    Ok(())
  }

  pub fn set_data(&mut self, data: &mut BLEAdvertisementData) -> Result<(), BLEError> {
    if self.adv_params.conn_mode == (ConnMode::Non as _) && !self.scan_response {
      data.flags = 0;
    } else {
      data.flags =
        (esp_idf_sys::BLE_HS_ADV_F_DISC_GEN | esp_idf_sys::BLE_HS_ADV_F_BREDR_UNSUP) as _;
    }

    let mut adv_data = data.as_ble_hs_adv_fields();
    let mut scan_data = esp_idf_sys::ble_hs_adv_fields::default();

    let mut payload_len = data.payload_len();

    if payload_len > BLE_HS_ADV_MAX_SZ {
      if self.scan_response {
        scan_data.name = adv_data.name;
        scan_data.name_len = adv_data.name_len;
        if scan_data.name_len > (BLE_HS_ADV_MAX_SZ - 2) as _ {
          scan_data.name_len = (BLE_HS_ADV_MAX_SZ - 2) as _;
          scan_data.set_name_is_complete(0);
        } else {
          scan_data.set_name_is_complete(1);
        }

        adv_data.name = core::ptr::null();
        adv_data.name_len = 0;
        adv_data.set_name_is_complete(0);
      } else {
        if adv_data.tx_pwr_lvl_is_present() > 0 {
          adv_data.set_tx_pwr_lvl_is_present(0);
          payload_len -= 2 + 1;
        }

        if payload_len > BLE_HS_ADV_MAX_SZ {
          adv_data.name_len -= (payload_len - BLE_HS_ADV_MAX_SZ) as u8;
          adv_data.set_name_is_complete(0);
        }
      }
    }

    unsafe {
      if self.scan_response {
        ble!(esp_idf_sys::ble_gap_adv_rsp_set_fields(&scan_data))?;
      }

      ble!(esp_idf_sys::ble_gap_adv_set_fields(&adv_data))
    }
  }

  pub fn set_raw_data(&mut self, data: &[u8]) -> Result<(), BLEError> {
    let rc = unsafe { esp_idf_sys::ble_gap_adv_set_data(data.as_ptr(), data.len() as i32) } as u32;

    // convert BLE_ERR_INV_HCI_CMD_PARMS to BLE_HS_EINVAL
    // https://github.com/taks/esp32-nimble/issues/104
    // https://github.com/apache/mynewt-nimble/issues/1717
    ble!(match rc {
      esp_idf_sys::ble_error_codes_BLE_ERR_INV_HCI_CMD_PARMS => esp_idf_sys::BLE_HS_EINVAL,
      rc => rc,
    })
  }

  pub fn set_raw_scan_response_data(&mut self, data: &[u8]) -> Result<(), BLEError> {
    unsafe {
      ble!(esp_idf_sys::ble_gap_adv_rsp_set_data(
        data.as_ptr(),
        data.len() as i32
      ))
    }
  }

  /// Set the type of advertisment to use.
  pub fn advertisement_type(&mut self, adv_type: ConnMode) -> &mut Self {
    self.adv_params.conn_mode = adv_type as _;
    self
  }

  /// Set discoverable mode.
  pub fn disc_mode(&mut self, mode: DiscMode) -> &mut Self {
    self.adv_params.disc_mode = mode as _;
    self
  }

  /// Set the duty cycle for advertisement_type.
  ///
  /// Valid only if advertisement_type is directed-connectable.
  pub fn high_duty_cycle(&mut self, val: bool) -> &mut Self {
    self.adv_params.set_high_duty_cycle(val as _);
    self
  }

  /// Set the minimum advertising interval.
  ///
  /// * `interval`: advertising interval in 0.625ms units, 0 = use default.
  pub fn min_interval(&mut self, interval: u16) -> &mut Self {
    self.adv_params.itvl_min = interval;
    self
  }

  /// Set the maximum advertising interval.
  ///
  /// * `interval`: advertising interval in 0.625ms units, 0 = use default.
  pub fn max_interval(&mut self, interval: u16) -> &mut Self {
    self.adv_params.itvl_max = interval;
    self
  }

  /// Set if scan response is available.
  pub fn scan_response(&mut self, value: bool) -> &mut Self {
    self.scan_response = value;
    self
  }

  /// Set the filtering for the scan filter.
  pub fn filter_policy(&mut self, value: AdvFilterPolicy) -> &mut Self {
    self.adv_params.filter_policy = value.into();
    self
  }

  /// Start advertising.
  /// Advertising not stop until it is manually stopped.
  pub fn start(&mut self) -> Result<(), BLEError> {
    self.start_with_duration(BLE_HS_FOREVER)
  }

  /// Start advertising.
  pub fn start_with_duration(&mut self, duration_ms: i32) -> Result<(), BLEError> {
    let mut server = unsafe { Lazy::get_mut(&mut crate::ble_device::BLE_SERVER) };
    if let Some(server) = server.as_mut() {
      if !server.started {
        server.start()?;
      }
    }

    if self.adv_params.conn_mode == (ConnMode::Non as _) && !self.scan_response {
      self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_NON as _;
    } else {
      self.adv_params.disc_mode = esp_idf_sys::BLE_GAP_DISC_MODE_GEN as _;
    }

    let handle_gap_event = if server.is_some() {
      BLEServer::handle_gap_event
    } else {
      Self::handle_gap_event
    };
    unsafe {
      ble!(esp_idf_sys::ble_gap_adv_start(
        crate::ble_device::OWN_ADDR_TYPE as _,
        core::ptr::null(),
        duration_ms,
        &self.adv_params,
        Some(handle_gap_event),
        self as *mut Self as _,
      ))?;
    }

    Ok(())
  }

  pub fn stop(&self) -> Result<(), BLEError> {
    unsafe { ble!(esp_idf_sys::ble_gap_adv_stop()) }
  }

  pub fn is_advertising(&self) -> bool {
    unsafe { esp_idf_sys::ble_gap_adv_active() != 0 }
  }

  pub fn on_complete(&mut self, callback: impl FnMut(c_int) + Send + Sync + 'static) -> &mut Self {
    self.on_complete = Some(Box::new(callback));
    self
  }

  pub(crate) extern "C" fn handle_gap_event(
    event: *mut esp_idf_sys::ble_gap_event,
    arg: *mut c_void,
  ) -> i32 {
    let event = unsafe { &*event };
    let adv = unsafe { voidp_to_ref::<Self>(arg) };

    if event.type_ == esp_idf_sys::BLE_GAP_EVENT_ADV_COMPLETE as _ {
      if let Some(callback) = adv.on_complete.as_mut() {
        callback(unsafe { event.__bindgen_anon_1.adv_complete.reason });
      }
    }

    0
  }
}

unsafe impl Send for BLEAdvertising {}