mirror of
https://github.com/seemoo-lab/openhaystack.git
synced 2026-02-14 09:39:52 +00:00
Supporting ESP32 as tags for OpenHaystack (#19)
* Moving microbit firmware to a subfolder in /Firmware to prepare integration of ESP32 * Add firmware for ESP32 and update workflows * Integrated ESP32 firmware from @fhessel to OpenHaystack App Co-authored-by: Frank Hessel <fhessel@seemoo.tu-darmstadt.de>
This commit is contained in:
committed by
GitHub
parent
f88663f5e7
commit
898563ca0b
38
.github/actions/build-esp-idf/action.yaml
vendored
Normal file
38
.github/actions/build-esp-idf/action.yaml
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
name: 'Build Firmware with ESP-IDF'
|
||||
description: 'Builds a firmware for the ESP32 using the ESP-IDF'
|
||||
inputs:
|
||||
src-dir:
|
||||
description: 'Source directory for the ESP-IDF project'
|
||||
required: true
|
||||
out-dir:
|
||||
description: 'Directory to which bin files will be written'
|
||||
required: true
|
||||
app-name:
|
||||
description: 'Name of the IDF application/main binary'
|
||||
required: true
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Prepare ESP-IDF
|
||||
shell: bash
|
||||
run: |
|
||||
sudo apt update
|
||||
sudo apt install git wget flex bison gperf python3 python3-pip python3-setuptools cmake ninja-build ccache libffi-dev libssl-dev dfu-util libusb-1.0-0
|
||||
mkdir -p /opt/esp
|
||||
cd /opt/esp
|
||||
git clone --recursive --depth 1 --branch v4.2 https://github.com/espressif/esp-idf.git
|
||||
cd /opt/esp/esp-idf
|
||||
./install.sh
|
||||
- name: Build firmware
|
||||
shell: bash
|
||||
run: |
|
||||
source /opt/esp/esp-idf/export.sh
|
||||
cd ${{ inputs.src-dir }}
|
||||
idf.py build
|
||||
- name: Bundle output files
|
||||
shell: bash
|
||||
run: |
|
||||
mkdir -p "${{ inputs.out-dir }}/bootloader" "${{ inputs.out-dir }}/partition_table"
|
||||
cp "${{ inputs.src-dir }}/build/bootloader/bootloader.bin" "${{ inputs.out-dir }}/bootloader/bootloader.bin"
|
||||
cp "${{ inputs.src-dir }}/build/partition_table/partition-table.bin" "${{ inputs.out-dir }}/partition_table/partition-table.bin"
|
||||
cp "${{ inputs.src-dir }}/build/${{ inputs.app-name }}.bin" "${{ inputs.out-dir }}/${{ inputs.app-name }}.bin"
|
||||
28
.github/workflows/build-firmware-esp32.yaml
vendored
Normal file
28
.github/workflows/build-firmware-esp32.yaml
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
name: "Build firmware (ESP32)"
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/ESP32/**
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/ESP32/**
|
||||
|
||||
jobs:
|
||||
build-firmware-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
- name: "Copy static files"
|
||||
run: |
|
||||
mkdir -p archive/build
|
||||
cp Firmware/ESP32/flash_esp32.sh archive/
|
||||
- name: "Build ESP32 firmware"
|
||||
uses: ./.github/actions/build-esp-idf
|
||||
with:
|
||||
src-dir: Firmware/ESP32
|
||||
out-dir: archive/build
|
||||
app-name: openhaystack
|
||||
6
.github/workflows/build-firmware.yaml
vendored
6
.github/workflows/build-firmware.yaml
vendored
@@ -4,15 +4,15 @@ on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/**
|
||||
- Firmware/Microbit_v1/**
|
||||
pull_request:
|
||||
branches: [ main ]
|
||||
paths:
|
||||
- Firmware/**
|
||||
- Firmware/Microbit_v1/**
|
||||
|
||||
defaults:
|
||||
run:
|
||||
working-directory: Firmware
|
||||
working-directory: Firmware/Microbit_v1
|
||||
|
||||
jobs:
|
||||
build-firmware:
|
||||
|
||||
29
.github/workflows/release.yml
vendored
29
.github/workflows/release.yml
vendored
@@ -6,6 +6,28 @@ on:
|
||||
- 'v*' # Push events to matching v*, i.e. v1.0, v20.15.10
|
||||
|
||||
jobs:
|
||||
build-firmware-esp32:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout code"
|
||||
uses: actions/checkout@v2
|
||||
- name: "Copy static files"
|
||||
run: |
|
||||
mkdir -p archive/build
|
||||
cp Firmware/ESP32/flash_esp32.sh archive/
|
||||
- name: "Build ESP32 firmware"
|
||||
uses: ./.github/actions/build-esp-idf
|
||||
with:
|
||||
src-dir: Firmware/ESP32
|
||||
out-dir: archive/build
|
||||
app-name: openhaystack
|
||||
- name: "Create archive"
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: firmware-esp32
|
||||
path: archive/*
|
||||
retention-days: 1
|
||||
|
||||
build-and-release:
|
||||
name: "Create release on GitHub"
|
||||
runs-on: macos-latest
|
||||
@@ -15,6 +37,8 @@ jobs:
|
||||
defaults:
|
||||
run:
|
||||
working-directory: ${{ env.PROJECT_DIR }}
|
||||
needs:
|
||||
- build-firmware-esp32
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v2
|
||||
@@ -22,6 +46,11 @@ jobs:
|
||||
uses: devbotsxyz/xcode-select@v1
|
||||
with:
|
||||
version: "12"
|
||||
- name: "Add ESP32 firmware"
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: firmware-esp32
|
||||
path: "${{ env.PROJECT_DIR }}/OpenHaystack/HaystackApp/Firmwares/ESP32"
|
||||
- name: "Archive project"
|
||||
run: xcodebuild archive -scheme ${APP} -configuration release -archivePath ${APP}.xcarchive
|
||||
- name: "Create ZIP"
|
||||
|
||||
4
.gitmodules
vendored
4
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "Firmware/blessed"]
|
||||
path = Firmware/blessed
|
||||
[submodule "Firmware/Microbit_v1/blessed"]
|
||||
path = Firmware/Microbit_v1/blessed
|
||||
url = https://github.com/pauloborges/blessed.git
|
||||
|
||||
3
Firmware/ESP32/.gitignore
vendored
Normal file
3
Firmware/ESP32/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
build/**
|
||||
venv/**
|
||||
sdkconfig.old
|
||||
7
Firmware/ESP32/CMakeLists.txt
Normal file
7
Firmware/ESP32/CMakeLists.txt
Normal file
@@ -0,0 +1,7 @@
|
||||
# The following lines of boilerplate have to be in your project's CMakeLists
|
||||
# in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.5)
|
||||
|
||||
set(SUPPORTED_TARGETS esp32)
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(openhaystack)
|
||||
10
Firmware/ESP32/Makefile
Normal file
10
Firmware/ESP32/Makefile
Normal file
@@ -0,0 +1,10 @@
|
||||
#
|
||||
# This is a project Makefile. It is assumed the directory this Makefile resides in is a
|
||||
# project subdirectory.
|
||||
#
|
||||
|
||||
PROJECT_NAME := openhaystack-esp32
|
||||
|
||||
COMPONENT_ADD_INCLUDEDIRS := components/include
|
||||
|
||||
include $(IDF_PATH)/make/project.mk
|
||||
44
Firmware/ESP32/README.md
Normal file
44
Firmware/ESP32/README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# OpenHaystack Firmware for ESP32
|
||||
|
||||
This project contains a PoC firmware for Espressif ESP32 chips (like ESP32-WROOM or ESP32-WROVER, but _not_ ESP32-S2).
|
||||
After flashing our firmware, the device sends out Bluetooth Low Energy advertisements such that it can be found by [Apple's Find My network](https://developer.apple.com/find-my/).
|
||||
|
||||
## Disclaimer
|
||||
|
||||
Note that the firmware is just a proof-of-concept and currently only implements advertising a single static key. This means that **devices running this firmware are trackable** by other devices in proximity.
|
||||
|
||||
## Requirements
|
||||
|
||||
To change and rebuild the firmware, you need Espressif's IoT Development Framework (ESP-IDF).
|
||||
Installation instructions for the latest version of the ESP-IDF can be found in [its documentation](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/get-started/).
|
||||
The firmware is tested on version 4.2.
|
||||
|
||||
For deploying the firmware, you need Python 3 on your path, either as `python3` (preferred) or as `python`, and the `venv` module needs to be available.
|
||||
|
||||
## Build
|
||||
|
||||
With the ESP-IDF on your `$PATH`, you can use `idf.py` to build the application from within this directory:
|
||||
|
||||
```bash
|
||||
idf.py build
|
||||
```
|
||||
|
||||
This will create the following files:
|
||||
|
||||
- `build/bootloader/bootloader.bin` -- The second stage bootloader
|
||||
- `build/partition_table/partition-table.bin` -- The partition table
|
||||
- `build/openhaystack.bin` -- The application itself
|
||||
|
||||
These files are required for the next step: Deploy the firmware.
|
||||
|
||||
## Deploy the Firmware
|
||||
|
||||
Use the `flash_esp32.sh` script to deploy the firmware and a public key to an ESP32 device connected to your local machine:
|
||||
|
||||
```bash
|
||||
./flash_esp32.sh -p /dev/yourSerialPort "public-key-in-base64"
|
||||
```
|
||||
|
||||
> **Note:** You might need to reset your device after running the script before it starts sending advertisements.
|
||||
|
||||
For more options, see `./flash-esp32.h --help`.
|
||||
139
Firmware/ESP32/flash_esp32.sh
Executable file
139
Firmware/ESP32/flash_esp32.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
3
Firmware/ESP32/main/CMakeLists.txt
Normal file
3
Firmware/ESP32/main/CMakeLists.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
idf_component_register(SRCS "openhaystack_main.c"
|
||||
|
||||
INCLUDE_DIRS ".")
|
||||
0
Firmware/ESP32/main/Kconfig.projbuild
Normal file
0
Firmware/ESP32/main/Kconfig.projbuild
Normal file
4
Firmware/ESP32/main/component.mk
Normal file
4
Firmware/ESP32/main/component.mk
Normal file
@@ -0,0 +1,4 @@
|
||||
#
|
||||
# "main" pseudo-component makefile.
|
||||
#
|
||||
# (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.)
|
||||
162
Firmware/ESP32/main/openhaystack_main.c
Normal file
162
Firmware/ESP32/main/openhaystack_main.c
Normal file
@@ -0,0 +1,162 @@
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_partition.h"
|
||||
|
||||
#include "esp_bt.h"
|
||||
#include "esp_gap_ble_api.h"
|
||||
#include "esp_gattc_api.h"
|
||||
#include "esp_gatt_defs.h"
|
||||
#include "esp_bt_main.h"
|
||||
#include "esp_bt_defs.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
|
||||
static const char* LOG_TAG = "open_haystack";
|
||||
|
||||
/** Callback function for BT events */
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param);
|
||||
|
||||
/** Random device address */
|
||||
static esp_bd_addr_t rnd_addr = { 0xFF, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF };
|
||||
|
||||
/** Advertisement payload */
|
||||
static uint8_t adv_data[31] = {
|
||||
0x1e, /* Length (30) */
|
||||
0xff, /* Manufacturer Specific Data (type 0xff) */
|
||||
0x4c, 0x00, /* Company ID (Apple) */
|
||||
0x12, 0x19, /* Offline Finding type and length */
|
||||
0x00, /* State */
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, /* First two bits */
|
||||
0x00, /* Hint (0x00) */
|
||||
};
|
||||
|
||||
/* https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/bluetooth/esp_gap_ble.html#_CPPv420esp_ble_adv_params_t */
|
||||
static esp_ble_adv_params_t ble_adv_params = {
|
||||
// Advertising min interval:
|
||||
// Minimum advertising interval for undirected and low duty cycle
|
||||
// directed advertising. Range: 0x0020 to 0x4000 Default: N = 0x0800
|
||||
// (1.28 second) Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec
|
||||
.adv_int_min = 0x00A0, // 100ms
|
||||
// Advertising max interval:
|
||||
// Maximum advertising interval for undirected and low duty cycle
|
||||
// directed advertising. Range: 0x0020 to 0x4000 Default: N = 0x0800
|
||||
// (1.28 second) Time = N * 0.625 msec Time Range: 20 ms to 10.24 sec
|
||||
.adv_int_max = 0x0140, // 200ms
|
||||
// Advertisement type
|
||||
.adv_type = ADV_TYPE_NONCONN_IND,
|
||||
// Use the random address
|
||||
.own_addr_type = BLE_ADDR_TYPE_RANDOM,
|
||||
// All channels
|
||||
.channel_map = ADV_CHNL_ALL,
|
||||
// Allow both scan and connection requests from anyone.
|
||||
.adv_filter_policy = ADV_FILTER_ALLOW_SCAN_ANY_CON_ANY,
|
||||
};
|
||||
|
||||
static void esp_gap_cb(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t *param)
|
||||
{
|
||||
esp_err_t err;
|
||||
|
||||
switch (event) {
|
||||
case ESP_GAP_BLE_ADV_DATA_RAW_SET_COMPLETE_EVT:
|
||||
esp_ble_gap_start_advertising(&ble_adv_params);
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_ADV_START_COMPLETE_EVT:
|
||||
//adv start complete event to indicate adv start successfully or failed
|
||||
if ((err = param->adv_start_cmpl.status) != ESP_BT_STATUS_SUCCESS) {
|
||||
ESP_LOGE(LOG_TAG, "advertising start failed: %s", esp_err_to_name(err));
|
||||
} else {
|
||||
ESP_LOGI(LOG_TAG, "advertising has started.");
|
||||
}
|
||||
break;
|
||||
|
||||
case ESP_GAP_BLE_ADV_STOP_COMPLETE_EVT:
|
||||
if ((err = param->adv_stop_cmpl.status) != ESP_BT_STATUS_SUCCESS){
|
||||
ESP_LOGE(LOG_TAG, "adv stop failed: %s", esp_err_to_name(err));
|
||||
}
|
||||
else {
|
||||
ESP_LOGI(LOG_TAG, "stop adv successfully");
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int load_key(uint8_t *dst, size_t size) {
|
||||
const esp_partition_t *keypart = esp_partition_find_first(0x40, 0x00, "key");
|
||||
if (keypart == NULL) {
|
||||
ESP_LOGE(LOG_TAG, "Could not find key partition");
|
||||
return 1;
|
||||
}
|
||||
esp_err_t status;
|
||||
status = esp_partition_read(keypart, 0, dst, size);
|
||||
if (status != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Could not read key from partition: %s", esp_err_to_name(status));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
void set_addr_from_key(esp_bd_addr_t addr, uint8_t *public_key) {
|
||||
addr[0] = public_key[0] | 0b11000000;
|
||||
addr[1] = public_key[1];
|
||||
addr[2] = public_key[2];
|
||||
addr[3] = public_key[3];
|
||||
addr[4] = public_key[4];
|
||||
addr[5] = public_key[5];
|
||||
}
|
||||
|
||||
void set_payload_from_key(uint8_t *payload, uint8_t *public_key) {
|
||||
/* copy last 22 bytes */
|
||||
memcpy(&payload[7], &public_key[6], 22);
|
||||
/* append two bits of public key */
|
||||
payload[29] = public_key[0] >> 6;
|
||||
}
|
||||
|
||||
void app_main(void)
|
||||
{
|
||||
ESP_ERROR_CHECK(nvs_flash_init());
|
||||
ESP_ERROR_CHECK(esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT));
|
||||
esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||
esp_bt_controller_init(&bt_cfg);
|
||||
esp_bt_controller_enable(ESP_BT_MODE_BLE);
|
||||
|
||||
esp_bluedroid_init();
|
||||
esp_bluedroid_enable();
|
||||
|
||||
// Load the public key from the key partition
|
||||
static uint8_t public_key[28];
|
||||
if (load_key(public_key, sizeof(public_key)) != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "Could not read the key, stopping.");
|
||||
return;
|
||||
}
|
||||
|
||||
set_addr_from_key(rnd_addr, public_key);
|
||||
set_payload_from_key(adv_data, public_key);
|
||||
|
||||
ESP_LOGI(LOG_TAG, "using device address: %02x %02x %02x %02x %02x %02x", rnd_addr[0], rnd_addr[1], rnd_addr[2], rnd_addr[3], rnd_addr[4], rnd_addr[5]);
|
||||
|
||||
esp_err_t status;
|
||||
//register the scan callback function to the gap module
|
||||
if ((status = esp_ble_gap_register_callback(esp_gap_cb)) != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "gap register error: %s", esp_err_to_name(status));
|
||||
return;
|
||||
}
|
||||
|
||||
if ((status = esp_ble_gap_set_rand_addr(rnd_addr)) != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "couldn't set random address: %s", esp_err_to_name(status));
|
||||
return;
|
||||
}
|
||||
if ((esp_ble_gap_config_adv_data_raw((uint8_t*)&adv_data, sizeof(adv_data))) != ESP_OK) {
|
||||
ESP_LOGE(LOG_TAG, "couldn't configure BLE adv: %s", esp_err_to_name(status));
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(LOG_TAG, "application initialized");
|
||||
}
|
||||
5
Firmware/ESP32/partitions.csv
Normal file
5
Firmware/ESP32/partitions.csv
Normal file
@@ -0,0 +1,5 @@
|
||||
# Name, Type, SubType, Offset, Size, Flags
|
||||
nvs, data, nvs, 0x9000, 0x5000,
|
||||
key, 0x40, 0x00, 0xe000, 0x1000,
|
||||
phy_init, data, phy, 0xf000, 0x1000,
|
||||
factory, app, factory, 0x10000, 1M,
|
||||
|
1606
Firmware/ESP32/sdkconfig
Normal file
1606
Firmware/ESP32/sdkconfig
Normal file
File diff suppressed because it is too large
Load Diff
@@ -10,6 +10,9 @@
|
||||
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78014A2725DC01220089F6D9 /* MicrobitController.swift */; };
|
||||
78014A2B25DC22120089F6D9 /* sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2A25DC22110089F6D9 /* sample.bin */; };
|
||||
78014A2F25DC2F100089F6D9 /* pattern_sample.bin in Resources */ = {isa = PBXBuildFile; fileRef = 78014A2E25DC2F100089F6D9 /* pattern_sample.bin */; };
|
||||
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78023CAA25F7767000B083EF /* ESP32Controller.swift */; };
|
||||
78023CAF25F7797400B083EF /* ESP32 in Resources */ = {isa = PBXBuildFile; fileRef = 78023CAE25F7797400B083EF /* ESP32 */; };
|
||||
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */; };
|
||||
781EB3EA25DAD7EA00FEAA19 /* ReportsFetcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 78108B84248E8FDD0007E9C4 /* ReportsFetcher.m */; };
|
||||
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 116B4EEC24A913AA007BA636 /* SavePanel.swift */; };
|
||||
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */ = {isa = PBXBuildFile; fileRef = 025DFEDB248FED250039C718 /* DecryptReports.swift */; };
|
||||
@@ -25,6 +28,8 @@
|
||||
781EB40025DAD7EA00FEAA19 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B76248E8FB80007E9C4 /* Preview Assets.xcassets */; };
|
||||
781EB40225DAD7EA00FEAA19 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 78108B73248E8FB80007E9C4 /* Assets.xcassets */; };
|
||||
781EB43125DADF2B00FEAA19 /* AnisetteDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */; };
|
||||
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD025F7B2C10054DC33 /* FileManager.swift */; };
|
||||
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */; };
|
||||
78286CB225E3ACE700F65511 /* OpenHaystackPluginService.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CAF25E3ACE700F65511 /* OpenHaystackPluginService.m */; };
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286CB025E3ACE700F65511 /* ALTAnisetteData.m */; };
|
||||
78286D2A25E3EC3200F65511 /* AppleAccountData.m in Sources */ = {isa = PBXBuildFile; fileRef = 78286D2925E3EC3200F65511 /* AppleAccountData.m */; };
|
||||
@@ -42,6 +47,7 @@
|
||||
7899D1D625DE74EE00115740 /* firmware.bin in Resources */ = {isa = PBXBuildFile; fileRef = 7899D1D525DE74EE00115740 /* firmware.bin */; };
|
||||
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7899D1E025DE97E200115740 /* IconSelectionView.swift */; };
|
||||
7899D1E925DEBF4900115740 /* AccessoryMapAnnotation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */; };
|
||||
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */; };
|
||||
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */; };
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC226B25DBC2E40042B775 /* OpenHaystackMainView.swift */; };
|
||||
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 78EC227125DBC8CE0042B775 /* Accessory.swift */; };
|
||||
@@ -101,6 +107,9 @@
|
||||
78014A2725DC01220089F6D9 /* MicrobitController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrobitController.swift; sourceTree = "<group>"; };
|
||||
78014A2A25DC22110089F6D9 /* sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = sample.bin; sourceTree = "<group>"; };
|
||||
78014A2E25DC2F100089F6D9 /* pattern_sample.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = pattern_sample.bin; sourceTree = "<group>"; };
|
||||
78023CAA25F7767000B083EF /* ESP32Controller.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32Controller.swift; sourceTree = "<group>"; };
|
||||
78023CAE25F7797400B083EF /* ESP32 */ = {isa = PBXFileReference; lastKnownFileType = folder; path = ESP32; sourceTree = "<group>"; };
|
||||
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MicrocontrollerTests.swift; sourceTree = "<group>"; };
|
||||
78108B6F248E8FB50007E9C4 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
78108B73248E8FB80007E9C4 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||
78108B76248E8FB80007E9C4 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||
@@ -113,6 +122,8 @@
|
||||
78108B90248F72AF0007E9C4 /* FindMyController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindMyController.swift; sourceTree = "<group>"; };
|
||||
781EB40825DAD7EA00FEAA19 /* OpenHaystack.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = OpenHaystack.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
781EB40F25DADB0600FEAA19 /* AnisetteDataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnisetteDataManager.swift; sourceTree = "<group>"; };
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileManager.swift; sourceTree = "<group>"; };
|
||||
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ESP32InstallSheet.swift; sourceTree = "<group>"; };
|
||||
78286C8E25E3AC0400F65511 /* OpenHaystackMail.mailbundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenHaystackMail.mailbundle; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
78286C9025E3AC0400F65511 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
78286CAE25E3ACE700F65511 /* OpenHaystackPluginService.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpenHaystackPluginService.h; sourceTree = "<group>"; };
|
||||
@@ -135,6 +146,7 @@
|
||||
7899D1D525DE74EE00115740 /* firmware.bin */ = {isa = PBXFileReference; lastKnownFileType = archive.macbinary; path = firmware.bin; sourceTree = "<group>"; };
|
||||
7899D1E025DE97E200115740 /* IconSelectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconSelectionView.swift; sourceTree = "<group>"; };
|
||||
7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessoryMapAnnotation.swift; sourceTree = "<group>"; };
|
||||
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageAccessoriesView.swift; sourceTree = "<group>"; };
|
||||
78EC226125DAE0BE0042B775 /* OpenHaystackTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OpenHaystackTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenHaystackTests.swift; sourceTree = "<group>"; };
|
||||
78EC226525DAE0BE0042B775 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@@ -180,6 +192,23 @@
|
||||
path = BoringSSL;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78023CAC25F7775300B083EF /* Firmwares */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
78023CAE25F7797400B083EF /* ESP32 */,
|
||||
78023CAD25F7775A00B083EF /* Microbit */,
|
||||
);
|
||||
path = Firmwares;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78023CAD25F7775A00B083EF /* Microbit */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7899D1D525DE74EE00115740 /* firmware.bin */,
|
||||
);
|
||||
path = Microbit;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
78108B63248E8FB50007E9C4 = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -291,6 +320,7 @@
|
||||
78014A2A25DC22110089F6D9 /* sample.bin */,
|
||||
78EC226325DAE0BE0042B775 /* OpenHaystackTests.swift */,
|
||||
78EC226525DAE0BE0042B775 /* Info.plist */,
|
||||
78023CB025F7841F00B083EF /* MicrocontrollerTests.swift */,
|
||||
);
|
||||
path = OpenHaystackTests;
|
||||
sourceTree = "<group>";
|
||||
@@ -298,13 +328,15 @@
|
||||
78EC226E25DBC2FC0042B775 /* HaystackApp */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
7899D1D525DE74EE00115740 /* firmware.bin */,
|
||||
78023CAC25F7775300B083EF /* Firmwares */,
|
||||
78286D3A25E4017400F65511 /* Mail Plugin */,
|
||||
78EC227025DBC8BB0042B775 /* Views */,
|
||||
78EC226F25DBC8B60042B775 /* Model */,
|
||||
78EC227625DBDB7E0042B775 /* KeychainController.swift */,
|
||||
78014A2725DC01220089F6D9 /* MicrobitController.swift */,
|
||||
787D8AC025DECD3C00148766 /* AccessoryController.swift */,
|
||||
78023CAA25F7767000B083EF /* ESP32Controller.swift */,
|
||||
7821DAD025F7B2C10054DC33 /* FileManager.swift */,
|
||||
);
|
||||
path = HaystackApp;
|
||||
sourceTree = "<group>";
|
||||
@@ -328,6 +360,8 @@
|
||||
7899D1E825DEBF4800115740 /* AccessoryMapAnnotation.swift */,
|
||||
78286E0125E66F9400F65511 /* AccessoryListEntry.swift */,
|
||||
7851F1DC25EE90FA0049480D /* AccessoryMapView.swift */,
|
||||
7821DAD225F7C39A0054DC33 /* ESP32InstallSheet.swift */,
|
||||
78D9B80525F7CF60009B9CE8 /* ManageAccessoriesView.swift */,
|
||||
);
|
||||
path = Views;
|
||||
sourceTree = "<group>";
|
||||
@@ -447,6 +481,7 @@
|
||||
isa = PBXResourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78023CAF25F7797400B083EF /* ESP32 in Resources */,
|
||||
781EB3FD25DAD7EA00FEAA19 /* Main.storyboard in Resources */,
|
||||
7899D1D625DE74EE00115740 /* firmware.bin in Resources */,
|
||||
781EB3FE25DAD7EA00FEAA19 /* MapViewController.xib in Resources */,
|
||||
@@ -583,18 +618,22 @@
|
||||
781EB3EB25DAD7EA00FEAA19 /* SavePanel.swift in Sources */,
|
||||
7899D1E125DE97E200115740 /* IconSelectionView.swift in Sources */,
|
||||
78EC227725DBDB7E0042B775 /* KeychainController.swift in Sources */,
|
||||
78D9B80625F7CF60009B9CE8 /* ManageAccessoriesView.swift in Sources */,
|
||||
78486BEF25DD711E0007ED87 /* PopUpAlertView.swift in Sources */,
|
||||
78014A2925DC08580089F6D9 /* MicrobitController.swift in Sources */,
|
||||
78286D1F25E3D8B800F65511 /* ALTAnisetteData.m in Sources */,
|
||||
781EB3EC25DAD7EA00FEAA19 /* DecryptReports.swift in Sources */,
|
||||
78EC226C25DBC2E40042B775 /* OpenHaystackMainView.swift in Sources */,
|
||||
78EC227225DBC8CE0042B775 /* Accessory.swift in Sources */,
|
||||
7821DAD125F7B2C10054DC33 /* FileManager.swift in Sources */,
|
||||
78286E0225E66F9400F65511 /* AccessoryListEntry.swift in Sources */,
|
||||
781EB3EF25DAD7EA00FEAA19 /* MapViewController.swift in Sources */,
|
||||
78286D7725E5114600F65511 /* ActivityIndicator.swift in Sources */,
|
||||
7821DAD325F7C39A0054DC33 /* ESP32InstallSheet.swift in Sources */,
|
||||
781EB3F125DAD7EA00FEAA19 /* FindMyKeyDecoder.swift in Sources */,
|
||||
787D8AC125DECD3C00148766 /* AccessoryController.swift in Sources */,
|
||||
781EB3F225DAD7EA00FEAA19 /* AppDelegate.swift in Sources */,
|
||||
78023CAB25F7767000B083EF /* ESP32Controller.swift in Sources */,
|
||||
781EB3F325DAD7EA00FEAA19 /* Models.swift in Sources */,
|
||||
781EB3F425DAD7EA00FEAA19 /* FindMyController.swift in Sources */,
|
||||
781EB3F525DAD7EA00FEAA19 /* BoringSSL.m in Sources */,
|
||||
@@ -615,6 +654,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
78023CB125F7841F00B083EF /* MicrocontrollerTests.swift in Sources */,
|
||||
78EC226425DAE0BE0042B775 /* OpenHaystackTests.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
||||
@@ -97,7 +97,7 @@ public class AnisetteDataManager: NSObject {
|
||||
"X-Apple-I-MD": data.oneTimePassword,
|
||||
"X-Apple-I-TimeZone": String(data.timeZone.abbreviation() ?? "UTC"),
|
||||
"X-Apple-I-Client-Time": ISO8601DateFormatter().string(from: data.date),
|
||||
"X-Apple-I-MD-RINFO": String(data.routingInfo),
|
||||
"X-Apple-I-MD-RINFO": String(data.routingInfo)
|
||||
] as [AnyHashable: Any])
|
||||
}
|
||||
}
|
||||
@@ -110,8 +110,7 @@ extension AnisetteDataManager {
|
||||
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
|
||||
|
||||
if let archivedAnisetteData = userInfo["anisetteData"] as? Data,
|
||||
let appleAccountData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: AppleAccountData.self, from: archivedAnisetteData)
|
||||
{
|
||||
let appleAccountData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: AppleAccountData.self, from: archivedAnisetteData) {
|
||||
if let range = appleAccountData.deviceDescription.lowercased().range(of: "(com.apple.mail") {
|
||||
var adjustedDescription = appleAccountData.deviceDescription[..<range.lowerBound]
|
||||
adjustedDescription += "(com.apple.dt.Xcode/3594.4.19)>"
|
||||
@@ -129,8 +128,7 @@ extension AnisetteDataManager {
|
||||
guard let userInfo = notification.userInfo, let requestUUID = userInfo["requestUUID"] as? String else { return }
|
||||
|
||||
if let archivedAnisetteData = userInfo["anisetteData"] as? Data,
|
||||
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData)
|
||||
{
|
||||
let anisetteData = try? NSKeyedUnarchiver.unarchivedObject(ofClass: ALTAnisetteData.self, from: archivedAnisetteData) {
|
||||
if let range = anisetteData.deviceDescription.lowercased().range(of: "(com.apple.mail") {
|
||||
var adjustedDescription = anisetteData.deviceDescription[..<range.lowerBound]
|
||||
adjustedDescription += "(com.apple.dt.Xcode/3594.4.19)>"
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "extended-gray",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"white" : "0.850"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "gray-gamma-22",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"white" : "0.100"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
{
|
||||
"colors" : [
|
||||
{
|
||||
"color" : {
|
||||
"color-space" : "extended-gray",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"white" : "0.780"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
},
|
||||
{
|
||||
"appearances" : [
|
||||
{
|
||||
"appearance" : "luminosity",
|
||||
"value" : "dark"
|
||||
}
|
||||
],
|
||||
"color" : {
|
||||
"color-space" : "gray-gamma-22",
|
||||
"components" : {
|
||||
"alpha" : "1.000",
|
||||
"white" : "0.200"
|
||||
}
|
||||
},
|
||||
"idiom" : "universal"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@
|
||||
import Combine
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
class FindMyController: ObservableObject {
|
||||
static let shared = FindMyController()
|
||||
@@ -82,6 +83,34 @@ class FindMyController: ObservableObject {
|
||||
}
|
||||
}
|
||||
|
||||
func fetchReports(for accessories: [Accessory], with token: Data, completion: @escaping (Result<[FindMyDevice], Error>) -> Void) {
|
||||
let findMyDevices = accessories.compactMap({ acc -> FindMyDevice? in
|
||||
do {
|
||||
return try acc.toFindMyDevice()
|
||||
} catch {
|
||||
os_log("Failed getting id for key %@", String(describing: error))
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
self.devices = findMyDevices
|
||||
|
||||
self.fetchReports(with: token) { error in
|
||||
|
||||
let reports = FindMyController.shared.devices.compactMap({ $0.reports }).flatMap({ $0 })
|
||||
if reports.isEmpty == false {
|
||||
AccessoryController.shared.updateWithDecryptedReports(devices: FindMyController.shared.devices)
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
completion(.failure(error))
|
||||
os_log("Error: %@", String(describing: error))
|
||||
} else {
|
||||
completion(.success(self.devices))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchReports(with searchPartyToken: Data, completion: @escaping (Error?) -> Void) {
|
||||
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
||||
@@ -6,14 +6,22 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Combine
|
||||
|
||||
class AccessoryController: ObservableObject {
|
||||
static let shared = AccessoryController()
|
||||
|
||||
@Published var accessories: [Accessory]
|
||||
|
||||
var accessoryObserver: AnyCancellable?
|
||||
|
||||
init() {
|
||||
self.accessories = KeychainController.loadAccessoriesFromKeychain()
|
||||
self.accessoryObserver = self.accessories.publisher
|
||||
.sink { _ in
|
||||
try? self.save()
|
||||
}
|
||||
}
|
||||
|
||||
init(accessories: [Accessory]) {
|
||||
@@ -46,4 +54,30 @@ class AccessoryController: ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func delete(accessory: Accessory) throws {
|
||||
var accessories = self.accessories
|
||||
guard let idx = accessories.firstIndex(of: accessory) else { return }
|
||||
|
||||
accessories.remove(at: idx)
|
||||
|
||||
withAnimation {
|
||||
self.accessories = accessories
|
||||
}
|
||||
try self.save()
|
||||
}
|
||||
|
||||
func addAccessory(with name: String, color: Color, icon: String) throws -> Accessory {
|
||||
let accessory = try Accessory(name: name, color: color, iconName: icon)
|
||||
|
||||
let accessories = self.accessories + [accessory]
|
||||
|
||||
withAnimation {
|
||||
self.accessories = accessories
|
||||
}
|
||||
|
||||
try self.save()
|
||||
|
||||
return accessory
|
||||
}
|
||||
}
|
||||
|
||||
66
OpenHaystack/OpenHaystack/HaystackApp/ESP32Controller.swift
Normal file
66
OpenHaystack/OpenHaystack/HaystackApp/ESP32Controller.swift
Normal file
@@ -0,0 +1,66 @@
|
||||
//
|
||||
// ESP32Controller.swift
|
||||
// OpenHaystack
|
||||
//
|
||||
// Created by Alex - SEEMOO on 09.03.21.
|
||||
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
struct ESP32Controller {
|
||||
static var espFirmwareDirectory: URL? {
|
||||
Bundle.main.resourceURL?.appendingPathComponent("ESP32")
|
||||
}
|
||||
|
||||
/// Tries to find the port / path at which the ESP32 module is attached
|
||||
static func findPort() -> [URL] {
|
||||
// List all ports
|
||||
let ports = try? FileManager.default.contentsOfDirectory(atPath: "/dev").filter({$0.contains("cu.")})
|
||||
|
||||
let portURLs = ports?.map({URL(fileURLWithPath: "/dev/\($0)")})
|
||||
|
||||
return portURLs ?? []
|
||||
}
|
||||
|
||||
/// Runs the script to flash the firmware on an ESP32
|
||||
static func flashToESP32(accessory: Accessory, port: URL, completion: @escaping (Result<Void, Error>) -> Void) throws {
|
||||
|
||||
// Copy firmware to a temporary directory
|
||||
let temp = NSTemporaryDirectory() + "OpenHaystack"
|
||||
let urlTemp = URL(fileURLWithPath: temp)
|
||||
try? FileManager.default.removeItem(at: urlTemp)
|
||||
|
||||
try? FileManager.default.createDirectory(atPath: temp, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
guard let espDirectory = espFirmwareDirectory else {return}
|
||||
|
||||
try FileManager.default.copyFolder(from: espDirectory, to: urlTemp)
|
||||
let scriptPath = urlTemp.appendingPathComponent("flash_esp32.sh")
|
||||
|
||||
let key = try accessory.getAdvertisementKey().base64EncodedString()
|
||||
let arguments = ["-p", "\(port.path)", key]
|
||||
|
||||
let task = try NSUserUnixTask(url: scriptPath)
|
||||
task.execute(withArguments: arguments) { e in
|
||||
DispatchQueue.main.async {
|
||||
if let error = e {
|
||||
completion(.failure(error))
|
||||
} else {
|
||||
completion(.success(()))
|
||||
}
|
||||
|
||||
// Delete the temporary folder
|
||||
try? FileManager.default.removeItem(at: urlTemp)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
enum FirmwareFlashError: Error {
|
||||
/// Missing files for flashing
|
||||
case notFound
|
||||
/// Flashing / writing failed
|
||||
case flashFailed
|
||||
}
|
||||
38
OpenHaystack/OpenHaystack/HaystackApp/FileManager.swift
Normal file
38
OpenHaystack/OpenHaystack/HaystackApp/FileManager.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// FileManager.swift
|
||||
// OpenHaystack
|
||||
//
|
||||
// Created by Alex - SEEMOO on 09.03.21.
|
||||
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
extension FileManager {
|
||||
|
||||
/// Copy a folder recursively.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Folder source
|
||||
/// - to: Folder destination
|
||||
/// - Throws: An error if copying or acessing files fails
|
||||
func copyFolder(from: URL, to: URL) throws {
|
||||
// Create the folder
|
||||
try? FileManager.default.createDirectory(at: to, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: from.path)
|
||||
for file in files {
|
||||
// Check if file is a folder
|
||||
var isDir: ObjCBool = .init(booleanLiteral: false)
|
||||
let fileURL = from.appendingPathComponent(file)
|
||||
FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir)
|
||||
|
||||
if isDir.boolValue == true {
|
||||
try self.copyFolder(from: fileURL, to: to.appendingPathComponent(file))
|
||||
} else {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
(directory will be populated in CI release workflow)
|
||||
139
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/ESP32/flash_esp32.sh
Executable file
139
OpenHaystack/OpenHaystack/HaystackApp/Firmwares/ESP32/flash_esp32.sh
Executable file
@@ -0,0 +1,139 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Directory of this script
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
|
||||
# Defaults: Directory for the virtual environment
|
||||
VENV_DIR="$SCRIPT_DIR/venv"
|
||||
|
||||
# Defaults: Serial port to access the ESP32
|
||||
PORT=/dev/ttyS0
|
||||
|
||||
# Defaults: Fast baud rate
|
||||
BAUDRATE=921600
|
||||
|
||||
# Parameter parsing
|
||||
while [[ $# -gt 0 ]]; do
|
||||
KEY="$1"
|
||||
case "$KEY" in
|
||||
-p|--port)
|
||||
PORT="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-s|--slow)
|
||||
BAUDRATE=115200
|
||||
shift
|
||||
;;
|
||||
-v|--venvdir)
|
||||
VENV_DIR="$2"
|
||||
shift
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
echo "flash_esp32.sh - Flash the OpenHaystack firmware onto an ESP32 module"
|
||||
echo ""
|
||||
echo " This script will create a virtual environment for the required tools."
|
||||
echo ""
|
||||
echo "Call: flash_esp32.sh [-p <port>] [-v <dir>] [-s] PUBKEY"
|
||||
echo ""
|
||||
echo "Required Arguments:"
|
||||
echo " PUBKEY"
|
||||
echo " The base64-encoded public key"
|
||||
echo ""
|
||||
echo "Optional Arguments:"
|
||||
echo " -h, --help"
|
||||
echo " Show this message and exit."
|
||||
echo " -p, --port <port>"
|
||||
echo " Specify the serial interface to which the device is connected."
|
||||
echo " -s, --slow"
|
||||
echo " Use 115200 instead of 921600 baud when flashing."
|
||||
echo " Might be required for long/bad USB cables or slow USB-to-Serial converters."
|
||||
echo " -v, --venvdir <dir>"
|
||||
echo " Select Python virtual environment with esptool installed."
|
||||
echo " If the directory does not exist, it will be created."
|
||||
exit 1
|
||||
;;
|
||||
*)
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
PUBKEY="$1"
|
||||
shift
|
||||
else
|
||||
echo "Got unexpected parameter $1"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Sanity check: Pubkey exists
|
||||
if [[ -z "$PUBKEY" ]]; then
|
||||
echo "Missing public key, call with --help for usage"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Sanity check: Port
|
||||
if [[ ! -e "$PORT" ]]; then
|
||||
echo "$PORT does not exist, please specify a valid serial interface with the -p argument"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Setup the virtual environment
|
||||
if [[ ! -d "$VENV_DIR" ]]; then
|
||||
# Create the virtual environment
|
||||
PYTHON="$(which python3)"
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
PYTHON="$(which python)"
|
||||
fi
|
||||
if [[ -z "$PYTHON" ]]; then
|
||||
echo "Could not find a Python installation, please install Python 3."
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -V 2>&1 | grep "Python 3" > /dev/null); then
|
||||
echo "Executing \"$PYTHON\" does not run Python 3, please make sure that python3 or python on your PATH points to Python 3"
|
||||
exit 1
|
||||
fi
|
||||
if ! ($PYTHON -c "import venv" &> /dev/null); then
|
||||
echo "Python 3 module \"venv\" was not found."
|
||||
exit 1
|
||||
fi
|
||||
$PYTHON -m venv "$VENV_DIR"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Creating the virtual environment in $VENV_DIR failed."
|
||||
exit 1
|
||||
fi
|
||||
source "$VENV_DIR/bin/activate"
|
||||
pip install --upgrade pip
|
||||
pip install esptool
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not install Python 3 module esptool in $VENV_DIR";
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
source "$VENV_DIR/bin/activate"
|
||||
fi
|
||||
|
||||
# Prepare the key
|
||||
KEYFILE="$SCRIPT_DIR/tmp.key"
|
||||
if [[ -f "$KEYFILE" ]]; then
|
||||
echo "$KEYFILE already exists, stopping here not to override files..."
|
||||
exit 1
|
||||
fi
|
||||
echo "$PUBKEY" | python3 -m base64 -d - > "$KEYFILE"
|
||||
if [[ $? != 0 ]]; then
|
||||
echo "Could not parse the public key. Please provide valid base64 input"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Call esptool.py. Errors from here on are critical
|
||||
set -e
|
||||
|
||||
# Clear NVM
|
||||
esptool.py --after no_reset \
|
||||
erase_region 0x9000 0x5000
|
||||
esptool.py --before no_reset --baud $BAUDRATE \
|
||||
write_flash 0x1000 "$SCRIPT_DIR/build/bootloader/bootloader.bin" \
|
||||
0x8000 "$SCRIPT_DIR/build/partition_table/partition-table.bin" \
|
||||
0xe000 "$KEYFILE" \
|
||||
0x10000 "$SCRIPT_DIR/build/openhaystack.bin"
|
||||
rm "$KEYFILE"
|
||||
@@ -17,7 +17,7 @@ struct KeychainController {
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
kSecMatchLimit: kSecMatchLimitOne,
|
||||
kSecReturnData: true,
|
||||
kSecReturnData: true
|
||||
]
|
||||
|
||||
if test {
|
||||
@@ -49,7 +49,7 @@ struct KeychainController {
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
kSecValueData: try PropertyListEncoder().encode(accessories),
|
||||
kSecValueData: try PropertyListEncoder().encode(accessories)
|
||||
]
|
||||
|
||||
if test {
|
||||
@@ -63,7 +63,7 @@ struct KeychainController {
|
||||
var query: [CFString: Any] = [
|
||||
kSecClass: kSecClassGenericPassword,
|
||||
kSecAttrLabel: "FindMyAccessories",
|
||||
kSecAttrService: "SEEMOO-FINDMY",
|
||||
kSecAttrService: "SEEMOO-FINDMY"
|
||||
]
|
||||
|
||||
if test {
|
||||
|
||||
@@ -63,7 +63,7 @@ struct MailPluginManager {
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
try self.copyFolder(from: localPluginURL, to: pluginURL)
|
||||
try FileManager.default.copyFolder(from: localPluginURL, to: pluginURL)
|
||||
|
||||
self.openAppleMail()
|
||||
}
|
||||
@@ -73,32 +73,6 @@ struct MailPluginManager {
|
||||
|
||||
}
|
||||
|
||||
/// Copy a folder recursively.
|
||||
///
|
||||
/// - Parameters:
|
||||
/// - from: Folder source
|
||||
/// - to: Folder destination
|
||||
/// - Throws: An error if copying or acessing files fails
|
||||
func copyFolder(from: URL, to: URL) throws {
|
||||
// Create the folder
|
||||
try? FileManager.default.createDirectory(at: to, withIntermediateDirectories: false, attributes: nil)
|
||||
|
||||
let files = try FileManager.default.contentsOfDirectory(atPath: from.path)
|
||||
for file in files {
|
||||
// Check if file is a folder
|
||||
var isDir: ObjCBool = .init(booleanLiteral: false)
|
||||
let fileURL = from.appendingPathComponent(file)
|
||||
FileManager.default.fileExists(atPath: fileURL.path, isDirectory: &isDir)
|
||||
|
||||
if isDir.boolValue == true {
|
||||
try self.copyFolder(from: fileURL, to: to.appendingPathComponent(file))
|
||||
} else {
|
||||
// Copy file
|
||||
try FileManager.default.copyItem(at: fileURL, to: to.appendingPathComponent(file))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func uninstallMailPlugin() throws {
|
||||
try FileManager.default.removeItem(at: pluginURL)
|
||||
}
|
||||
@@ -115,7 +89,7 @@ struct MailPluginManager {
|
||||
|
||||
let downloadsPluginURL = downloadsFolder.appendingPathComponent(mailBundleName + ".mailbundle")
|
||||
|
||||
try self.copyFolder(from: localPluginURL, to: downloadsPluginURL)
|
||||
try FileManager.default.copyFolder(from: localPluginURL, to: downloadsPluginURL)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -72,6 +72,22 @@ struct MicrobitController {
|
||||
return patchedFirmware
|
||||
}
|
||||
|
||||
static func deploy(accessory: Accessory) throws {
|
||||
let microbits = try MicrobitController.findMicrobits()
|
||||
guard let microBitURL = microbits.first,
|
||||
let firmwareURL = Bundle.main.url(forResource: "firmware", withExtension: "bin")
|
||||
else {
|
||||
throw FirmwareFlashError.notFound
|
||||
}
|
||||
|
||||
let firmware = try Data(contentsOf: firmwareURL)
|
||||
let pattern = "OFFLINEFINDINGPUBLICKEYHERE!".data(using: .ascii)!
|
||||
let publicKey = try accessory.getAdvertisementKey()
|
||||
let patchedFirmware = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: publicKey)
|
||||
|
||||
try MicrobitController.deployToMicrobit(microBitURL, firmwareFile: patchedFirmware)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
enum PatchingError: Error {
|
||||
|
||||
@@ -41,8 +41,7 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
|
||||
if var colorComponents = try? container.decode([CGFloat].self, forKey: .colorComponents),
|
||||
let spaceName = try? container.decode(String.self, forKey: .colorSpaceName),
|
||||
let cgColor = CGColor(colorSpace: CGColorSpace(name: spaceName as CFString)!, components: &colorComponents)
|
||||
{
|
||||
let cgColor = CGColor(colorSpace: CGColorSpace(name: spaceName as CFString)!, components: &colorComponents) {
|
||||
self.color = Color(cgColor)
|
||||
} else {
|
||||
self.color = Color.white
|
||||
@@ -58,8 +57,7 @@ class Accessory: ObservableObject, Codable, Identifiable, Equatable {
|
||||
try container.encode(self.icon, forKey: .icon)
|
||||
|
||||
if let colorComponents = self.color.cgColor?.components,
|
||||
let colorSpace = self.color.cgColor?.colorSpace?.name
|
||||
{
|
||||
let colorSpace = self.color.cgColor?.colorSpace?.name {
|
||||
try container.encode(colorComponents, forKey: .colorComponents)
|
||||
try container.encode(colorSpace as String, forKey: .colorSpaceName)
|
||||
}
|
||||
|
||||
@@ -26,6 +26,5 @@ struct AccessoryMapView: NSViewControllerRepresentable {
|
||||
nsViewController.addLastLocations(from: accessories)
|
||||
|
||||
nsViewController.changeMapType(mapType)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// ESP32InstallSheet.swift
|
||||
// OpenHaystack
|
||||
//
|
||||
// Created by Alex - SEEMOO on 09.03.21.
|
||||
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import OSLog
|
||||
|
||||
struct ESP32InstallSheet: View {
|
||||
@Binding var accessory: Accessory?
|
||||
@Binding var alertType: OpenHaystackMainView.AlertType?
|
||||
@State var detectedPorts: [URL] = []
|
||||
|
||||
@State var isFlashing = false
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
self.portSelectionView
|
||||
.padding()
|
||||
.overlay(self.loadingOverlay)
|
||||
.frame(minWidth: 640, minHeight: 480, alignment: .center)
|
||||
}
|
||||
.onAppear {
|
||||
self.detectedPorts = ESP32Controller.findPort()
|
||||
}
|
||||
}
|
||||
|
||||
var portSelectionView: some View {
|
||||
VStack {
|
||||
Text("Flash your ESP32")
|
||||
.font(.title2)
|
||||
|
||||
Text("Select the serial port that belongs to your ESP32 module")
|
||||
.foregroundColor(.gray)
|
||||
|
||||
self.portList
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Button("Reload ports", action: {
|
||||
self.detectedPorts = ESP32Controller.findPort()
|
||||
})
|
||||
|
||||
Button("Cancel", action: {
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var portList: some View {
|
||||
ScrollView {
|
||||
VStack(spacing: 4) {
|
||||
ForEach(0..<self.detectedPorts.count, id: \.self) { portIdx in
|
||||
Button(action: {
|
||||
if let accessory = self.accessory {
|
||||
// Flash selected module
|
||||
self.deployAccessoryToESP32(accessory: accessory, to: self.detectedPorts[portIdx])
|
||||
}
|
||||
}, label: {
|
||||
HStack {
|
||||
Text(self.detectedPorts[portIdx].path)
|
||||
.padding(4)
|
||||
Spacer()
|
||||
}
|
||||
.contentShape(Rectangle())
|
||||
|
||||
})
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var loadingOverlay: some View {
|
||||
ZStack {
|
||||
if isFlashing {
|
||||
Rectangle()
|
||||
.fill(Color.gray)
|
||||
.opacity(0.5)
|
||||
|
||||
VStack {
|
||||
ActivityIndicator(size: .large)
|
||||
Text("This can take up to 3min")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func deployAccessoryToESP32(accessory: Accessory, to port: URL) {
|
||||
do {
|
||||
self.isFlashing = true
|
||||
try ESP32Controller.flashToESP32(accessory: accessory, port: port, completion: { result in
|
||||
presentationMode.wrappedValue.dismiss()
|
||||
|
||||
self.isFlashing = false
|
||||
switch result {
|
||||
case .success(_):
|
||||
self.alertType = .deployedSuccessfully
|
||||
case .failure(let error):
|
||||
os_log(.error, "Flashing to ESP32 failed %@", String(describing: error))
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
self.alertType = .deployFailed
|
||||
}
|
||||
})
|
||||
} catch {
|
||||
os_log(.error, "Execution of script failed %@", String(describing: error))
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
self.alertType = .deployFailed
|
||||
self.isFlashing = false
|
||||
|
||||
}
|
||||
|
||||
self.accessory = nil
|
||||
}
|
||||
}
|
||||
|
||||
struct ESP32InstallSheet_Previews: PreviewProvider {
|
||||
@State static var acc: Accessory? = try! Accessory(name: "Sample")
|
||||
|
||||
@State static var alert: OpenHaystackMainView.AlertType?
|
||||
|
||||
static var previews: some View {
|
||||
ESP32InstallSheet(accessory: $acc, alertType: $alert)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
//
|
||||
// ManageAccessoriesView.swift
|
||||
// OpenHaystack
|
||||
//
|
||||
// Created by Alex - SEEMOO on 09.03.21.
|
||||
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct ManageAccessoriesView: View {
|
||||
|
||||
@ObservedObject var accessoryController = AccessoryController.shared
|
||||
var accessories: [Accessory] {
|
||||
return self.accessoryController.accessories
|
||||
}
|
||||
|
||||
// MARK: Bindings from main View
|
||||
@Binding var alertType: OpenHaystackMainView.AlertType?
|
||||
@Binding var focusedAccessory: Accessory?
|
||||
@Binding var accessoryToDeploy: Accessory?
|
||||
@Binding var showESP32DeploySheet: Bool
|
||||
|
||||
// MARK: View State
|
||||
@State var keyName: String = ""
|
||||
@State var accessoryColor: Color = Color.white
|
||||
@State var selectedIcon: String = "briefcase.fill"
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
Text("Create a new tracking accessory")
|
||||
.font(.title2)
|
||||
.padding(.top)
|
||||
|
||||
Text("A BBC Microbit can be used to track anything you care about. Connect it over USB, name the accessory (e.g. Backpack) generate the key and deploy it")
|
||||
.multilineTextAlignment(.center)
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
HStack {
|
||||
TextField("Name", text: self.$keyName)
|
||||
ColorPicker("", selection: self.$accessoryColor)
|
||||
.frame(maxWidth: 50, maxHeight: 20)
|
||||
IconSelectionView(selectedImageName: self.$selectedIcon)
|
||||
}
|
||||
|
||||
Button(
|
||||
action: self.addAccessory,
|
||||
label: {
|
||||
Text("Generate key and deploy")
|
||||
}
|
||||
)
|
||||
.disabled(self.keyName.isEmpty)
|
||||
.padding(.bottom)
|
||||
|
||||
Divider()
|
||||
|
||||
Text("Your accessories")
|
||||
.font(.title2)
|
||||
.padding(.top)
|
||||
|
||||
if self.accessories.isEmpty {
|
||||
Spacer()
|
||||
Text("No accessories have been added yet. Go ahead and add one above")
|
||||
.multilineTextAlignment(.center)
|
||||
} else {
|
||||
self.accessoryList
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
.sheet(isPresented: self.$showESP32DeploySheet, content: {
|
||||
ESP32InstallSheet(accessory: self.$accessoryToDeploy, alertType: self.$alertType)
|
||||
})
|
||||
}
|
||||
|
||||
/// Accessory List view.
|
||||
var accessoryList: some View {
|
||||
List(self.accessories) { accessory in
|
||||
AccessoryListEntry(
|
||||
accessory: accessory,
|
||||
alertType: self.$alertType,
|
||||
delete: self.delete(accessory:),
|
||||
deployAccessoryToMicrobit: self.deploy(accessory:),
|
||||
zoomOn: { self.focusedAccessory = $0 })
|
||||
}
|
||||
.background(Color.clear)
|
||||
.cornerRadius(15.0)
|
||||
}
|
||||
|
||||
/// Delete an accessory from the list of accessories.
|
||||
func delete(accessory: Accessory) {
|
||||
do {
|
||||
try self.accessoryController.delete(accessory: accessory)
|
||||
} catch {
|
||||
self.alertType = .deletionFailed
|
||||
}
|
||||
}
|
||||
|
||||
func deploy(accessory: Accessory) {
|
||||
self.accessoryToDeploy = accessory
|
||||
self.alertType = .selectDepoyTarget
|
||||
}
|
||||
|
||||
/// Add an accessory with the provided details.
|
||||
func addAccessory() {
|
||||
let keyName = self.keyName
|
||||
self.keyName = ""
|
||||
|
||||
do {
|
||||
let accessory = try self.accessoryController.addAccessory(with: keyName, color: self.accessoryColor, icon: self.selectedIcon)
|
||||
self.deploy(accessory: accessory)
|
||||
|
||||
} catch {
|
||||
self.alertType = .keyError
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
struct ManageAccessoriesView_Previews: PreviewProvider {
|
||||
|
||||
@State static var accessories = PreviewData.accessories
|
||||
@State static var alertType: OpenHaystackMainView.AlertType?
|
||||
@State static var focussed: Accessory?
|
||||
@State static var deploy: Accessory?
|
||||
@State static var showESPSheet: Bool = true
|
||||
|
||||
static var previews: some View {
|
||||
ManageAccessoriesView(alertType: self.$alertType, focusedAccessory: self.$focussed, accessoryToDeploy: self.$deploy, showESP32DeploySheet: self.$showESPSheet)
|
||||
}
|
||||
}
|
||||
@@ -11,10 +11,6 @@ import SwiftUI
|
||||
|
||||
struct OpenHaystackMainView: View {
|
||||
|
||||
@State var keyName: String = ""
|
||||
@State var accessoryColor: Color = Color.white
|
||||
@State var selectedIcon: String = "briefcase.fill"
|
||||
|
||||
@State var loading = false
|
||||
@ObservedObject var accessoryController = AccessoryController.shared
|
||||
var accessories: [Accessory] {
|
||||
@@ -30,14 +26,20 @@ struct OpenHaystackMainView: View {
|
||||
@State var mapType: MKMapType = .standard
|
||||
@State var isLoading = false
|
||||
@State var focusedAccessory: Accessory?
|
||||
@State var accessoryToDeploy: Accessory?
|
||||
|
||||
@State var showESP32DeploySheet = false
|
||||
|
||||
var body: some View {
|
||||
GeometryReader { geo in
|
||||
ZStack {
|
||||
VStack {
|
||||
HStack {
|
||||
self.accessoryView
|
||||
.frame(width: geo.size.width * 0.5)
|
||||
ManageAccessoriesView(
|
||||
alertType: self.$alertType,
|
||||
focusedAccessory: self.$focusedAccessory,
|
||||
accessoryToDeploy: self.$accessoryToDeploy,
|
||||
showESP32DeploySheet: self.$showESP32DeploySheet)
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -92,67 +94,6 @@ struct OpenHaystackMainView: View {
|
||||
|
||||
// MARK: Subviews
|
||||
|
||||
/// Left side of the view. Shows a list of accessories and the possibility to add accessories
|
||||
var accessoryView: some View {
|
||||
VStack {
|
||||
Text("Create a new tracking accessory")
|
||||
.font(.title2)
|
||||
.padding(.top)
|
||||
|
||||
Text("A BBC Microbit can be used to track anything you care about. Connect it over USB, name the accessory (e.g. Backpack) generate the key and deploy it")
|
||||
.multilineTextAlignment(.center)
|
||||
.font(.caption)
|
||||
.foregroundColor(.gray)
|
||||
|
||||
HStack {
|
||||
TextField("Name", text: self.$keyName)
|
||||
ColorPicker("", selection: self.$accessoryColor)
|
||||
.frame(maxWidth: 50, maxHeight: 20)
|
||||
IconSelectionView(selectedImageName: self.$selectedIcon)
|
||||
}
|
||||
|
||||
Button(
|
||||
action: self.addAccessory,
|
||||
label: {
|
||||
Text("Generate key and deploy")
|
||||
}
|
||||
)
|
||||
.disabled(self.keyName.isEmpty)
|
||||
.padding(.bottom)
|
||||
|
||||
Divider()
|
||||
|
||||
Text("Your accessories")
|
||||
.font(.title2)
|
||||
.padding(.top)
|
||||
|
||||
if self.accessories.isEmpty {
|
||||
Spacer()
|
||||
Text("No accessories have been added yet. Go ahead and add one above")
|
||||
.multilineTextAlignment(.center)
|
||||
} else {
|
||||
self.accessoryList
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/// Accessory List view.
|
||||
var accessoryList: some View {
|
||||
List(self.accessories) { accessory in
|
||||
AccessoryListEntry(
|
||||
accessory: accessory,
|
||||
alertType: self.$alertType,
|
||||
delete: self.delete(accessory:),
|
||||
deployAccessoryToMicrobit: self.deployAccessoryToMicrobit(accessory:),
|
||||
zoomOn: { self.focusedAccessory = $0 })
|
||||
}
|
||||
.background(Color.clear)
|
||||
.cornerRadius(15.0)
|
||||
}
|
||||
|
||||
/// Overlay for the map that is gray and shows an activity indicator when loading.
|
||||
var mapOverlay: some View {
|
||||
ZStack {
|
||||
@@ -202,28 +143,25 @@ struct OpenHaystackMainView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add an accessory with the provided details.
|
||||
func addAccessory() {
|
||||
let keyName = self.keyName
|
||||
self.keyName = ""
|
||||
func onAppear() {
|
||||
|
||||
do {
|
||||
let accessory = try Accessory(name: keyName, color: self.accessoryColor, iconName: self.selectedIcon)
|
||||
|
||||
let accessories = self.accessories + [accessory]
|
||||
|
||||
withAnimation {
|
||||
self.accessoryController.accessories = accessories
|
||||
}
|
||||
try self.accessoryController.save()
|
||||
|
||||
self.deployAccessoryToMicrobit(accessory: accessory)
|
||||
|
||||
} catch {
|
||||
self.errorDescription = String(describing: error)
|
||||
self.showKeyError = true
|
||||
/// Checks if the search party token can be fetched without the Mail Plugin. If true the plugin is not needed for this environment. (e.g. when SIP is disabled)
|
||||
let reportsFetcher = ReportsFetcher()
|
||||
if let token = reportsFetcher.fetchSearchpartyToken(),
|
||||
let tokenString = String(data: token, encoding: .ascii) {
|
||||
self.searchPartyToken = tokenString
|
||||
return
|
||||
}
|
||||
|
||||
let pluginManager = MailPluginManager()
|
||||
|
||||
// Check if the plugin is installed
|
||||
if pluginManager.isMailPluginInstalled == false {
|
||||
// Install the mail plugin
|
||||
self.alertType = .activatePlugin
|
||||
} else {
|
||||
self.checkPluginIsRunning(nil)
|
||||
}
|
||||
}
|
||||
|
||||
/// Download the location reports for all current accessories. Shows an error if something fails, like plug-in is missing
|
||||
@@ -246,75 +184,35 @@ struct OpenHaystackMainView: View {
|
||||
self.isLoading = true
|
||||
}
|
||||
|
||||
let findMyDevices = self.accessories.compactMap({ acc -> FindMyDevice? in
|
||||
do {
|
||||
return try acc.toFindMyDevice()
|
||||
} catch {
|
||||
os_log("Failed getting id for key %@", String(describing: error))
|
||||
return nil
|
||||
}
|
||||
})
|
||||
|
||||
FindMyController.shared.devices = findMyDevices
|
||||
FindMyController.shared.fetchReports(with: tokenData) { error in
|
||||
|
||||
let reports = FindMyController.shared.devices.compactMap({ $0.reports }).flatMap({ $0 })
|
||||
if reports.isEmpty {
|
||||
withAnimation {
|
||||
self.popUpAlertType = .noReportsFound
|
||||
FindMyController.shared.fetchReports(for: accessories, with: tokenData) { result in
|
||||
switch result {
|
||||
case .failure(let error):
|
||||
os_log(.error, "Downloading reports failed %@", error.localizedDescription)
|
||||
case .success(let devices):
|
||||
let reports = devices.compactMap({ $0.reports }).flatMap({ $0 })
|
||||
if reports.isEmpty {
|
||||
withAnimation {
|
||||
self.popUpAlertType = .noReportsFound
|
||||
}
|
||||
}
|
||||
} else {
|
||||
self.accessoryController.updateWithDecryptedReports(devices: FindMyController.shared.devices)
|
||||
}
|
||||
|
||||
withAnimation {
|
||||
self.isLoading = false
|
||||
}
|
||||
|
||||
guard error != nil else { return }
|
||||
os_log("Error: %@", String(describing: error))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// Delete an accessory from the list of accessories.
|
||||
func delete(accessory: Accessory) {
|
||||
do {
|
||||
var accessories = self.accessories
|
||||
guard let idx = accessories.firstIndex(of: accessory) else { return }
|
||||
|
||||
accessories.remove(at: idx)
|
||||
|
||||
withAnimation {
|
||||
self.accessoryController.accessories = accessories
|
||||
}
|
||||
try self.accessoryController.save()
|
||||
|
||||
} catch {
|
||||
self.alertType = .deletionFailed
|
||||
}
|
||||
|
||||
func deploy(accessory: Accessory) {
|
||||
self.accessoryToDeploy = accessory
|
||||
self.alertType = .selectDepoyTarget
|
||||
}
|
||||
|
||||
/// Deploy the public key of the accessory to a BBC microbit.
|
||||
func deployAccessoryToMicrobit(accessory: Accessory) {
|
||||
do {
|
||||
let microbits = try MicrobitController.findMicrobits()
|
||||
guard let microBitURL = microbits.first,
|
||||
let firmwareURL = Bundle.main.url(forResource: "firmware", withExtension: "bin")
|
||||
else {
|
||||
self.alertType = .deployFailed
|
||||
return
|
||||
}
|
||||
let firmware = try Data(contentsOf: firmwareURL)
|
||||
let pattern = "OFFLINEFINDINGPUBLICKEYHERE!".data(using: .ascii)!
|
||||
let publicKey = try accessory.getAdvertisementKey()
|
||||
let patchedFirmware = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: publicKey)
|
||||
|
||||
try MicrobitController.deployToMicrobit(microBitURL, firmwareFile: patchedFirmware)
|
||||
|
||||
try MicrobitController.deploy(accessory: accessory)
|
||||
} catch {
|
||||
os_log("Error occurred %@", String(describing: error))
|
||||
self.alertType = .deployFailed
|
||||
@@ -322,28 +220,8 @@ struct OpenHaystackMainView: View {
|
||||
}
|
||||
|
||||
self.alertType = .deployedSuccessfully
|
||||
}
|
||||
|
||||
func onAppear() {
|
||||
|
||||
/// Checks if the search party token can be fetched without the Mail Plugin. If true the plugin is not needed for this environment. (e.g. when SIP is disabled)
|
||||
let reportsFetcher = ReportsFetcher()
|
||||
if let token = reportsFetcher.fetchSearchpartyToken(),
|
||||
let tokenString = String(data: token, encoding: .ascii)
|
||||
{
|
||||
self.searchPartyToken = tokenString
|
||||
return
|
||||
}
|
||||
|
||||
let pluginManager = MailPluginManager()
|
||||
|
||||
// Check if the plugin is installed
|
||||
if pluginManager.isMailPluginInstalled == false {
|
||||
// Install the mail plugin
|
||||
self.alertType = .activatePlugin
|
||||
} else {
|
||||
self.checkPluginIsRunning(nil)
|
||||
}
|
||||
self.accessoryToDeploy = nil
|
||||
}
|
||||
|
||||
/// Ask to install and activate the mail plugin.
|
||||
@@ -402,6 +280,7 @@ struct OpenHaystackMainView: View {
|
||||
|
||||
// MARK: - Alerts
|
||||
|
||||
// swiftlint:disable function_body_length
|
||||
/// Create an alert for the given alert type.
|
||||
///
|
||||
/// - Parameter alertType: current alert type
|
||||
@@ -465,6 +344,17 @@ struct OpenHaystackMainView: View {
|
||||
action: {
|
||||
self.downloadPlugin()
|
||||
}), secondaryButton: .cancel())
|
||||
case .selectDepoyTarget:
|
||||
let microbitButton = Alert.Button.default(Text("Microbit"), action: {self.deployAccessoryToMicrobit(accessory: self.accessoryToDeploy!)})
|
||||
|
||||
let esp32Button = Alert.Button.default(Text("ESP32"), action: {
|
||||
self.showESP32DeploySheet = true
|
||||
})
|
||||
|
||||
return Alert(title: Text("Select target"),
|
||||
message: Text("Please select to which device you want to deploy"),
|
||||
primaryButton: microbitButton,
|
||||
secondaryButton: esp32Button)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -481,6 +371,7 @@ struct OpenHaystackMainView: View {
|
||||
case noReportsFound
|
||||
case activatePlugin
|
||||
case pluginInstallFailed
|
||||
case selectDepoyTarget
|
||||
}
|
||||
|
||||
}
|
||||
@@ -491,7 +382,7 @@ struct OpenHaystackMainView_Previews: PreviewProvider {
|
||||
|
||||
static var previews: some View {
|
||||
OpenHaystackMainView(accessoryController: AccessoryController(accessories: accessories))
|
||||
.frame(width: 640, height: 480, alignment: .center)
|
||||
.frame(width: 800, height: 600, alignment: .center)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
104
OpenHaystack/OpenHaystackTests/MicrocontrollerTests.swift
Normal file
104
OpenHaystack/OpenHaystackTests/MicrocontrollerTests.swift
Normal file
@@ -0,0 +1,104 @@
|
||||
//
|
||||
// MicrocontrollerTests.swift
|
||||
// OpenHaystackTests
|
||||
//
|
||||
// Created by Alex - SEEMOO on 09.03.21.
|
||||
// Copyright © 2021 SEEMOO - TU Darmstadt. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
@testable import OpenHaystack
|
||||
|
||||
class MicrocontrollerTests: XCTestCase {
|
||||
|
||||
override func setUpWithError() throws {
|
||||
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
override func tearDownWithError() throws {
|
||||
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||
}
|
||||
|
||||
func testMicrobitDeploy() throws {
|
||||
let urls = try MicrobitController.findMicrobits()
|
||||
|
||||
if let mBitURL = urls.first {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "sample", withExtension: "bin")!)
|
||||
try MicrobitController.deployToMicrobit(mBitURL, firmwareFile: firmware)
|
||||
}
|
||||
}
|
||||
|
||||
func testBinaryPatching() throws {
|
||||
// Patching sample.bin should fail
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x1])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
XCTFail("Should thrown an erorr before")
|
||||
} catch PatchingError.patternNotFound {
|
||||
// This should be thrown
|
||||
} catch {
|
||||
XCTFail("Unexpected error")
|
||||
}
|
||||
|
||||
// Patching the sample should be successful
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "pattern_sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xcc])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
// Patching key too short
|
||||
|
||||
// Patching the sample should be successful
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "pattern_sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xcc])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch PatchingError.inequalLength {
|
||||
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
// Testing with the actual firmware
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "offline-finding", withExtension: "bin")!)
|
||||
let pattern = "OFFLINEFINDINGPUBLICKEYHERE!".data(using: .ascii)!
|
||||
let key = Data(repeating: 0xaa, count: 28)
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch PatchingError.inequalLength {
|
||||
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testFindESP32Port() {
|
||||
let port = ESP32Controller.findPort()
|
||||
XCTAssertNotNil(port)
|
||||
}
|
||||
|
||||
func testESP32Deploy() throws {
|
||||
let accessory = try Accessory(name: "Sample")
|
||||
let expect = expectation(description: "ESP32 Flash")
|
||||
let port = ESP32Controller.findPort().first(where: {$0.absoluteString.contains("usb")})!
|
||||
try ESP32Controller.flashToESP32(accessory: accessory, port: port) { result in
|
||||
expect.fulfill()
|
||||
switch result {
|
||||
case .success(_):
|
||||
break
|
||||
case .failure(let error):
|
||||
XCTFail(error.localizedDescription)
|
||||
}
|
||||
}
|
||||
wait(for: [expect], timeout: 60.0)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,67 +90,6 @@ class OpenHaystackTests: XCTestCase {
|
||||
try KeychainController.storeInKeychain(accessories: [], test: true)
|
||||
}
|
||||
|
||||
func testMicrobitDeploy() throws {
|
||||
let urls = try MicrobitController.findMicrobits()
|
||||
|
||||
if let mBitURL = urls.first {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "sample", withExtension: "bin")!)
|
||||
try MicrobitController.deployToMicrobit(mBitURL, firmwareFile: firmware)
|
||||
}
|
||||
}
|
||||
|
||||
func testBinaryPatching() throws {
|
||||
// Patching sample.bin should fail
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xa, 0xb, 0xc, 0xd, 0xe, 0xf, 0x0, 0x1])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
XCTFail("Should thrown an erorr before")
|
||||
} catch PatchingError.patternNotFound {
|
||||
// This should be thrown
|
||||
} catch {
|
||||
XCTFail("Unexpected error")
|
||||
}
|
||||
|
||||
// Patching the sample should be successful
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "pattern_sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xcc])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
// Patching key too short
|
||||
|
||||
// Patching the sample should be successful
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "pattern_sample", withExtension: "bin")!)
|
||||
let pattern = Data([0xaa, 0xaa, 0xaa, 0xaa, 0xbb, 0xbb, 0xbb, 0xcc])
|
||||
let key = Data([1, 1, 1, 1, 1, 1, 1])
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch PatchingError.inequalLength {
|
||||
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
// Testing with the actual firmware
|
||||
do {
|
||||
let firmware = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "offline-finding", withExtension: "bin")!)
|
||||
let pattern = "OFFLINEFINDINGPUBLICKEYHERE!".data(using: .ascii)!
|
||||
let key = Data(repeating: 0xaa, count: 28)
|
||||
_ = try MicrobitController.patchFirmware(firmware, pattern: pattern, with: key)
|
||||
} catch PatchingError.inequalLength {
|
||||
|
||||
} catch {
|
||||
XCTFail("Unexpected error \(String(describing: error))")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func testKeyIDGeneration() throws {
|
||||
// Import keys with their respective id from a plist
|
||||
let plist = try Data(contentsOf: Bundle(for: Self.self).url(forResource: "sampleKeys", withExtension: "plist")!)
|
||||
|
||||
Reference in New Issue
Block a user