eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

wibble.rs (9282B)


      1 use embassy_time::{Duration,Timer};
      2 use trouble_host::prelude::*;
      3 use esp_radio::ble::controller::BleConnector;
      4 use esp_radio::Controller as RadioController;
      5 use log::{warn,info};
      6 use embassy_futures::join::join;
      7 use embassy_futures::select::select;
      8 
      9 use crypto_box::{
     10     aead::Aead,
     11     SalsaBox, PublicKey, SecretKey, Nonce
     12 };
     13 
     14 use alloc::vec::Vec;
     15 use alloc::string::String;
     16 
     17 use crate::ota::Flasher;
     18 
     19 /// Max number of connections
     20 const CONNECTIONS_MAX: usize = 1;
     21 
     22 /// Max number of L2CAP channels.
     23 // XXX why not 0?
     24 const L2CAP_CHANNELS_MAX: usize = 2; // Signal + att
     25 
     26 // GATT Server definition
     27 #[gatt_server]
     28 struct Server {
     29     wifi_provisioning_service: WifiProvisioningService,
     30 }
     31 
     32 
     33 #[gatt_service(uuid = "76be25c2-0000-44fc-a97c-bcb56c69c926")]
     34 struct WifiProvisioningService {
     35     #[characteristic(uuid = "76be25c2-0801-44fc-a97c-bcb56c69c926", read, notify, value = false)]
     36     connecting: bool,
     37 
     38     #[characteristic(uuid = "76be25c2-0802-44fc-a97c-bcb56c69c926", read, notify, value = 0)]
     39     max_network_index: u8,
     40 
     41     #[characteristic(uuid = "76be25c2-0803-44fc-a97c-bcb56c69c926", read, write, value = 0)]
     42     current_network_index: u8,
     43 
     44     // for arrays > 32 we must supply a default value : see
     45     // https://github.com/embassy-rs/trouble/issues/320
     46     #[characteristic(uuid = "76be25c2-0804-44fc-a97c-bcb56c69c926",
     47 		     value = [0; 40],
     48 		     read, notify)]
     49     current_network: [u8; 40],
     50 
     51     #[characteristic(uuid = "76be25c2-0805-44fc-a97c-bcb56c69c926",
     52 		     value = [0; (1 + 32 + 1 + 24 + 16 + 64)],
     53 		     write)]
     54     secret: [u8; 1+ 32 + 1 + 24 + 16 + 64]
     55 }
     56 
     57 
     58 pub async fn run_ble(radio_init: &RadioController<'static>,
     59 		     peripheral:  esp_hal::peripherals::BT<'_>,
     60 		     networks: Vec<[u8; 40]>,
     61 		     mut flasher: Flasher<'_>) {
     62     let connector = BleConnector::new(&radio_init, peripheral, Default::default()).expect("connector");
     63     let controller: ExternalController<_, 20> = ExternalController::new(connector);
     64 
     65     // Using a fixed "random" address can be useful for testing. In real scenarios, one would
     66     // use e.g. the MAC 6 byte array as the address (how to get that varies by the platform).
     67     let address: Address = Address::random([0xff, 0x8f, 0x1a, 0x05, 0xe4, 0xff]);
     68     info!("Our address = {:?}", address);
     69 
     70     let mut resources: HostResources<DefaultPacketPool, CONNECTIONS_MAX, L2CAP_CHANNELS_MAX> = HostResources::new();
     71     let stack = trouble_host::new(controller, &mut resources).set_random_address(address);
     72     let Host {
     73         mut peripheral, runner, ..
     74     } = stack.build();
     75 
     76     info!("Starting advertising and GATT service");
     77     let server = Server::new_with_config(GapConfig::Peripheral(PeripheralConfig {
     78         name: "eculocate",
     79         appearance: &appearance::power_device::GENERIC_POWER_DEVICE,
     80     }))
     81     .unwrap();
     82 
     83     let _ = join(
     84 	ble_task(runner),
     85 	async {
     86             loop {
     87 		match advertise("Eculocate", &mut peripheral, &server).await {
     88                     Ok(conn) => {
     89 			// set up tasks when the connection is established to a central, so they don't run when no one is connected.
     90 			let a = gatt_events_task(&server, &conn, &networks, &mut flasher);
     91 			let b = custom_task(&server, &conn, &stack);
     92 			// run until any task ends (usually because the connection has been closed),
     93 			// then return to advertising state.
     94 			select(a, b).await;
     95                     }
     96                     Err(e) => {
     97 			panic!("[adv] error: {:?}", e);
     98                     }
     99 		}
    100             }
    101 	})
    102     .await;
    103 }
    104 
    105 /// This is a background task that is required to run forever alongside any other BLE tasks.
    106 ///
    107 async fn ble_task<C: Controller, P: PacketPool>(mut runner: Runner<'_, C, P>) {
    108     loop {
    109         if let Err(e) = runner.run().await {
    110             panic!("[ble_task] error: {:?}", e);
    111         }
    112     }
    113 }
    114 
    115 const CLIENT_PUBLIC_KEY_BYTES: &[u8; 32] = include_bytes!("../../keys/client.pub");
    116 const SECRET_KEY_BYTES : &[u8; 32] = include_bytes!("../../keys/device.key");
    117 
    118 #[derive(Debug)]
    119 enum DecryptError {
    120     Crypto(crypto_box::aead::Error),
    121     Encoding(alloc::str::Utf8Error)
    122 }
    123 impl From<crypto_box::aead::Error>  for DecryptError {
    124     fn from(e: crypto_box::aead::Error) -> Self {
    125 	Self::Crypto(e)
    126     }
    127 }
    128 impl From<alloc::str::Utf8Error>  for DecryptError {
    129     fn from(e: alloc::str::Utf8Error) -> Self { Self::Encoding(e) }
    130 }
    131 
    132 fn decrypt_secret(ciphertext : &[u8]) -> Result<Vec<u8>, DecryptError>  {
    133     let client_public_key = PublicKey::from(*CLIENT_PUBLIC_KEY_BYTES);
    134     let secret_key= SecretKey::from(*SECRET_KEY_BYTES);
    135 
    136     let len : usize = ciphertext[0].into();
    137     let ciphertext = &ciphertext[1..len+1];
    138 
    139     let (nonce, encrypted) = ciphertext.split_at(24);
    140     let nonce = Nonce::from_slice(nonce);
    141 
    142     let sbox = SalsaBox::new(&client_public_key, &secret_key);
    143     let plain_bytes = sbox.decrypt(nonce, encrypted)?;
    144 
    145     Ok(Vec::from(plain_bytes))
    146 }
    147 
    148 
    149 /// Stream Events until the connection closes.
    150 ///
    151 /// This function will handle the GATT events and process them.
    152 /// This is how we interact with read and write requests.
    153 async fn gatt_events_task<P: PacketPool>(server: &Server<'_>,
    154 					 conn: &GattConnection<'_, '_, P>,
    155 					 networks: &Vec<[u8; 40]>,
    156 					 flasher: &mut Flasher<'_>) -> Result<(), Error> {
    157     let wps = &server.wifi_provisioning_service;
    158     let max_index : u8 = (networks.len() & 0xff).try_into().unwrap();
    159 
    160     server.set(&wps.max_network_index, &max_index);
    161     let current_index = wps.current_network_index;
    162     let reason = loop {
    163         match conn.next().await {
    164             GattConnectionEvent::Disconnected { reason } => break reason,
    165             GattConnectionEvent::Gatt { event } => {
    166                 match &event {
    167                     GattEvent::Write(event) => {
    168 			info!("[gatt] write handle {:?} {:?}", event.handle(),
    169 			      event.data());
    170 			if event.handle() == current_index.handle {
    171 			    let i = (event.data()[0]) as usize;
    172 			    info!("[gatt] switch current index: {:?}", i);
    173 			    if i == 0 {
    174 				server.set(&wps.current_network, &[0u8;40]);
    175 			    } else if i <= networks.len() {
    176 				server.set(&wps.current_network,
    177 					   &(networks[i - 1]));
    178 			    } else {
    179 				warn!("index {:?} out of range", i);
    180 			    }
    181 			} else if event.handle() == wps.connecting.handle {
    182 			    let val = server.get(&wps.secret).unwrap();
    183 			    let name_len : usize = val[0].into();
    184 			    let name = &val[1..name_len+1];
    185 
    186 			    match decrypt_secret(&val[name_len+1..]) {
    187 				Ok(pw) => {
    188 				    write_wifi_config(flasher, name, &pw[..]).await;
    189 				},
    190 				Err(e) => {
    191 				    warn!("decrypt error: {:?}", e);
    192 				}
    193 			    }
    194                         }
    195                     },
    196                     _ => {}
    197                 };
    198                 // This step is also performed at drop(), but writing
    199                 // it explicitly is necessary in order to ensure reply
    200                 // is sent.
    201                 match event.accept() {
    202                     Ok(reply) => reply.send().await,
    203                     Err(e) => warn!("[gatt] error sending response: {:?}", e),
    204                 };
    205             }
    206             _ => {} // ignore other Gatt Connection Events
    207         }
    208     };
    209     info!("[gatt] disconnected: {:?}", reason);
    210     Ok(())
    211 }
    212 
    213 async fn write_wifi_config(flasher: &mut Flasher<'_>, ssid: &[u8], password: &[u8]) -> Result<(),esp_nvs::error::Error> {
    214     let ssid = core::str::from_utf8(ssid).unwrap();
    215     let password = core::str::from_utf8(password).unwrap();
    216     flasher.set_var("ssid", Some(ssid))?;
    217     flasher.set_var("secret", Some(password))?;
    218     info!("[gatt] deep breath then reboot");
    219     Timer::after(Duration::from_millis(2000)).await;
    220     esp_hal::system::software_reset();
    221 }
    222 
    223 /// Create an advertiser to use to connect to a BLE Central, and wait for it to connect.
    224 async fn advertise<'values, 'server, C: Controller>(
    225     name: &'values str,
    226     peripheral: &mut Peripheral<'values, C, DefaultPacketPool>,
    227     server: &'server Server<'values>,
    228 ) -> Result<GattConnection<'values, 'server, DefaultPacketPool>, BleHostError<C::Error>> {
    229     let mut advertiser_data = [0; 31];
    230     let len = AdStructure::encode_slice(
    231         &[
    232             AdStructure::Flags(LE_GENERAL_DISCOVERABLE | BR_EDR_NOT_SUPPORTED),
    233             AdStructure::ServiceUuids16(&[[0x0f, 0x18]]),
    234             AdStructure::CompleteLocalName(name.as_bytes()),
    235         ],
    236         &mut advertiser_data[..],
    237     )?;
    238     let advertiser = peripheral
    239         .advertise(
    240             &Default::default(),
    241             Advertisement::ConnectableScannableUndirected {
    242                 adv_data: &advertiser_data[..len],
    243                 scan_data: &[],
    244             },
    245         )
    246         .await?;
    247     info!("[adv] advertising");
    248     let conn = advertiser.accept().await?.with_attribute_server(server)?;
    249     info!("[adv] connection established");
    250     Ok(conn)
    251 }
    252 
    253 async fn custom_task<C: Controller, P: PacketPool>(
    254     _server: &Server<'_>,
    255     conn: &GattConnection<'_, '_, P>,
    256     stack: &Stack<'_, C, P>,
    257 ) {
    258     loop {
    259         // read RSSI (Received Signal Strength Indicator) of the connection.
    260         if let Ok(rssi) = conn.raw().rssi(stack).await {
    261             info!("[custom_task] RSSI: {:?}", rssi);
    262         } else {
    263             info!("[custom_task] error getting RSSI");
    264             break;
    265         };
    266 
    267         Timer::after_secs(2).await;
    268     }
    269 }