From efd4b0ac74cac43f59f3eae12948322833447f20 Mon Sep 17 00:00:00 2001 From: Malte Heinzelmann Date: Tue, 4 Jun 2024 00:03:48 +0200 Subject: [PATCH] Updated agenda app --- .github/workflows/cd-release.yml | 14 +- .github/workflows/ci-build.yml | 8 +- .idea/vcs.xml | 5 +- CMakeLists.txt | 4 +- README.md | 4 +- backtrace.py | 2 +- main/CMakeLists.txt | 1 + main/factory_test.c | 4 +- main/file_browser.c | 48 +++- main/main.c | 3 +- main/menus/agenda.c | 477 +++++++++++++++++++++++++++++-- main/menus/start.c | 5 +- package.sh | 4 +- resources/icons/bookmark.png | Bin 0 -> 462 bytes resources/icons/bookmark.svg | 39 +++ 15 files changed, 560 insertions(+), 58 deletions(-) create mode 100644 resources/icons/bookmark.png create mode 100644 resources/icons/bookmark.svg diff --git a/.github/workflows/cd-release.yml b/.github/workflows/cd-release.yml index e136baf..102182e 100644 --- a/.github/workflows/cd-release.yml +++ b/.github/workflows/cd-release.yml @@ -32,8 +32,8 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: > gh release upload "${{ github.event.release.tag_name }}" - build/TROOPERS23.bin - build/TROOPERS23.elf + build/TROOPERS24.bin + build/TROOPERS24.elf - name: Dispatch OTA hook uses: peter-evans/repository-dispatch@v2 @@ -43,11 +43,11 @@ jobs: event-type: firmware-release client-payload: > { - "device_id": "troopers23", - "device_name": "TROOPERS23", + "device_id": "troopers24", + "device_name": "TROOPERS24", "tag": "${{ github.event.release.tag_name }}", "channel": "${{ env.RELEASE_CHANNEL }}", - "fw_main": "TROOPERS23.bin" + "fw_main": "TROOPERS24.bin" } - name: Generate release build report @@ -60,7 +60,7 @@ jobs: previous_tag=$(git tag --sort '-refname' | grep -A1 "$tag" | tail -1) tag_compare_url=$(sed "s!{base}!$previous_tag!; s!{head}!$tag!" <<< $compare_url_template) - build_size_main=$(du build/TROOPERS23.bin | awk '{ print $1 }') + build_size_main=$(du build/TROOPERS24.bin | awk '{ print $1 }') EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) @@ -72,7 +72,7 @@ jobs: **Source:** [${repo}@\`${tag}\`](/${repo}/tree/${tag}) ## Build details - **Size of \`TROOPERS23.bin\`:** $build_size_main kB + **Size of \`TROOPERS24.bin\`:** $build_size_main kB \`\`\`console \$ du -h build/*.bin build/*.elf build/*/*.bin build/*/*.elf diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index e008868..1f1c1a2 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -38,8 +38,8 @@ jobs: with: name: build path: | - build/TROOPERS23.bin - build/TROOPERS23.elf + build/TROOPERS24.bin + build/TROOPERS24.elf build/bootloader/bootloader.bin build/bootloader/bootloader.elf @@ -52,7 +52,7 @@ jobs: head_compare_url: ${{ github.event.compare }} new_commits_json: ${{ toJSON(github.event.commits) }} run: | - build_size_main=$(du build/TROOPERS23.bin | awk '{ print $1 }') + build_size_main=$(du build/TROOPERS24.bin | awk '{ print $1 }') ref_compare_url=$(sed "s/{base}/$base_branch/; s/{head}/$commit_hash/" <<< $compare_url_template) EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64) @@ -62,7 +62,7 @@ jobs: **Source:** ${{ github.ref_type }} \`$current_ref\` -> [${{ github.repository }}@\`${commit_hash:0:7}\`](${{ github.event.head_commit.url }}) - **Size of \`TROOPERS23.bin\`:** $build_size_main kB + **Size of \`TROOPERS24.bin\`:** $build_size_main kB \`\`\`console \$ du -h build/*.bin build/*/*.bin diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 955fe3b..d1630b8 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -154,10 +154,11 @@ + - - + + diff --git a/CMakeLists.txt b/CMakeLists.txt index 3749efd..ff39503 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.5) include($ENV{IDF_PATH}/tools/cmake/project.cmake) -set(PROJECT_NAME "TROOPERS23") -set(PROJECT_VER "1.1.1") +set(PROJECT_NAME "TROOPERS24") +set(PROJECT_VER "1.0.0") project(${PROJECT_NAME}) diff --git a/README.md b/README.md index da0d039..b77524e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -# TROOPERS23 ESP32 firmware: Launcher +# TROOPERS24 ESP32 firmware: Launcher -This repository contains the ESP32 part of the firmware for the TROOPERS23 badge. This firmware allows for device testing, setup, OTA updates and of course launching apps. +This repository contains the ESP32 part of the firmware for the TROOPERS24 badge. This firmware allows for device testing, setup, OTA updates and of course launching apps. ## ESP-IDF and submodules diff --git a/backtrace.py b/backtrace.py index ca35cd9..d2237cf 100644 --- a/backtrace.py +++ b/backtrace.py @@ -14,7 +14,7 @@ from elftools.elf.elffile import ELFFile -elf = ELFFile(open("build/TROOPERS23.elf", 'rb')) +elf = ELFFile(open("build/TROOPERS24.elf", 'rb')) symtab = elf.get_section_by_name('.symtab') for a in x: diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index c771281..69d40c8 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -59,6 +59,7 @@ idf_component_register( ${project_dir}/resources/icons/sao.png ${project_dir}/resources/icons/calendar.png ${project_dir}/resources/icons/clock.png + ${project_dir}/resources/icons/bookmark.png ${project_dir}/resources/id/id_shield.png ${project_dir}/resources/id/id_ernw.png ${project_dir}/resources/id/id_fucss.png diff --git a/main/factory_test.c b/main/factory_test.c index a0e161f..aed80ba 100644 --- a/main/factory_test.c +++ b/main/factory_test.c @@ -83,13 +83,13 @@ bool test_sdcard_init(uint32_t* rc) { return false; } - char* test_str = "TROOPERS23 TEST"; + char* test_str = "TROOPERS24 TEST"; int test_str_len = 15; fwrite(test_str, 1, test_str_len, test_fd); fclose(test_fd); - ESP_LOGI(TAG, "Wrote file, trying to read. You should see \"TROOPERS23 TEST\" in the next line"); + ESP_LOGI(TAG, "Wrote file, trying to read. You should see \"TROOPERS24 TEST\" in the next line"); test_fd = fopen("/sd/factory_test", "r"); if (test_fd == NULL) { diff --git a/main/file_browser.c b/main/file_browser.c index bbb8c95..88e2edd 100644 --- a/main/file_browser.c +++ b/main/file_browser.c @@ -180,6 +180,22 @@ static void file_browser_open_file(xQueueHandle button_queue, const char* filena return; } +static void file_browser_delete_file(file_browser_menu_args_t* menuArgs) { + pax_buf_t* pax_buffer = get_pax_buffer(); + const pax_font_t* font = pax_font_saira_regular; + pax_noclip(pax_buffer); + pax_background(pax_buffer, 0xFFFFFF); + + if (menuArgs->type == 'f') { + remove(menuArgs->path); + } else { + pax_draw_text(pax_buffer, 0xFFFF0000, font, 18, 0, 0, "Currently cannot delete directory\n\nPress A or B to go back"); + display_flush(); + ESP_LOGE(TAG, "Currently cannot delete directory"); + wait_for_button(); + } +} + void file_browser(xQueueHandle button_queue, const char* initial_path) { pax_buf_t* pax_buffer = get_pax_buffer(); display_boot_screen("Please wait..."); @@ -225,6 +241,7 @@ void file_browser(xQueueHandle button_queue, const char* initial_path) { bool renderbg = true; bool exit = false; file_browser_menu_args_t* menuArgs = NULL; + bool delete = false; while (1) { keyboard_input_message_t buttonMessage = {0}; @@ -250,11 +267,17 @@ void file_browser(xQueueHandle button_queue, const char* initial_path) { } break; case BUTTON_ACCEPT: - case BUTTON_SELECT: if (value) { menuArgs = menu_get_callback_args(menu, menu_get_position(menu)); } break; + case BUTTON_SELECT: + case JOYSTICK_PUSH: + if (value && menu_get_position(menu) > 0) { + menuArgs = menu_get_callback_args(menu, menu_get_position(menu)); + delete = true; + } + break; case BUTTON_START: if (value) exit = true; break; @@ -267,7 +290,7 @@ void file_browser(xQueueHandle button_queue, const char* initial_path) { pax_background(pax_buffer, 0xFFFFFF); pax_noclip(pax_buffer); const pax_font_t* font = pax_font_saira_regular; - pax_draw_text(pax_buffer, 0xFF000000, font, 18, 5, 240 - 19, "🅰 install 🅱 back"); + pax_draw_text(pax_buffer, 0xFF000000, font, 18, 5, 240 - 19, "🅰 install 🅱 back 🅴 delete"); renderbg = false; } @@ -278,16 +301,23 @@ void file_browser(xQueueHandle button_queue, const char* initial_path) { } if (menuArgs != NULL) { - if (menuArgs->type == 'd') { - strcpy(path, menuArgs->path); + if (delete) { + ESP_LOGD(TAG, "File selected: %c -> %s\n", menuArgs->type, menuArgs->path); + file_browser_delete_file(menuArgs); break; } else { - printf("File selected: %s\n", menuArgs->path); - file_browser_open_file(button_queue, menuArgs->path, menuArgs->label); + if (menuArgs->type == 'd') { + strcpy(path, menuArgs->path); + break; + } else { + printf("File selected: %s\n", menuArgs->path); + file_browser_open_file(button_queue, menuArgs->path, menuArgs->label); + } + menuArgs = NULL; + delete = false; + render = true; + renderbg = true; } - menuArgs = NULL; - render = true; - renderbg = true; } if (exit) { diff --git a/main/main.c b/main/main.c index c8ce2d9..fca48e3 100644 --- a/main/main.c +++ b/main/main.c @@ -235,8 +235,7 @@ _Noreturn void app_main(void) { stop(); } - // TODO: do we still need this? - // factory_test(); +// factory_test(); /* Start AppFS */ res = appfs_init(); diff --git a/main/menus/agenda.c b/main/menus/agenda.c index 07040b4..f7be24f 100644 --- a/main/menus/agenda.c +++ b/main/menus/agenda.c @@ -15,6 +15,8 @@ static const char* TAG = "agenda"; +static const char* BOOKMARKED = "bookmarked"; + #define DEBUG_INFRA 0 static const char* last_update_path = "/internal/apps/agenda/last_update"; @@ -22,6 +24,7 @@ static const char* day1_path = "/internal/apps/agenda/0.json"; static const char* day1_path_tmp = "/internal/apps/agenda/0.json.tmp"; static const char* day2_path = "/internal/apps/agenda/1.json"; static const char* day2_path_tmp = "/internal/apps/agenda/1.json.tmp"; +static const char* my_agenda_path = "/internal/apps/agenda/my.json"; #if DEBUG_INFRA static const char* last_update_url = "http://con.troopers.de/agenda/last_update"; @@ -33,8 +36,9 @@ static const char* day1_url = "https://con.troopers.de/agenda/0.json"; static const char* day2_url = "https://con.troopers.de/agenda/1.json"; #endif -static const char* DEFAULT_DAY1 = "{\"tracks\": [{\"title\": \"Attack & Research\", \"talks\": [{\"title\": \"Keynote\", \"time\": \"09:00\", \"ts\": 1687942800, \"speakers\": []}, {\"title\": \"Coffee Break\", \"time\": \"10:00\", \"ts\": 1687946400, \"speakers\": []}, {\"title\": \"OopsSec - The bad, the worst and the ugly of APT\u2019s operations security\", \"time\": \"10:30\", \"ts\": 1687948200, \"speakers\": [\"Tomer Bar\"]}, {\"title\": \"Spooky authentication at a distance\", \"time\": \"11:30\", \"ts\": 1687951800, \"speakers\": [\"Tamas Jos\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1687955400, \"speakers\": []}, {\"title\": \"Attacking Ultra-Wideband: Security Analysis of UWB Applications in Smartphones\", \"time\": \"13:30\", \"ts\": 1687959000, \"speakers\": [\"Alexander Heinrich\", \"Jiska Classen\"]}, {\"title\": \"Fact Based Post Exploitation - Office365 Edition\", \"time\": \"14:30\", \"ts\": 1687962600, \"speakers\": [\"Melvin Langvik\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1687966200, \"speakers\": []}, {\"title\": \"Forensic Examination of Ceph\", \"time\": \"16:00\", \"ts\": 1687968000, \"speakers\": [\"Florian Bausch\"]}, {\"title\": \"All your parcel are belong to us\", \"time\": \"17:00\", \"ts\": 1687971600, \"speakers\": [\"Dennis Kniel\"]}, {\"title\": \"Stay fit: Hack a Jump Rope\", \"time\": \"17:30\", \"ts\": 1687973400, \"speakers\": [\"Axelle Apvrille\"]}]}, {\"title\": \"Defense & Management\", \"talks\": [{\"title\": \"Coffee Break\", \"time\": \"10:00\", \"ts\": 1687946400, \"speakers\": []}, {\"title\": \"Das IT-Security-Lagebild aus Heise-Sicht\", \"time\": \"10:30\", \"ts\": 1687948200, \"speakers\": [\"J\u00fcrgen Schmidt aka ju\"]}, {\"title\": \"Cat & Mouse - or chess?\", \"time\": \"11:30\", \"ts\": 1687951800, \"speakers\": [\"Fabian Mosch\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1687955400, \"speakers\": []}, {\"title\": \"Real world detection engineering in a multi-cloud environment\", \"time\": \"13:30\", \"ts\": 1687959000, \"speakers\": [\"Aaron Jewitt\"]}, {\"title\": \"Security Heroes versus the Power of Privacy\", \"time\": \"14:30\", \"ts\": 1687962600, \"speakers\": [\"Avi D\", \"Kim Wuyts\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1687966200, \"speakers\": []}, {\"title\": \"SAP (Anti-)Forensics: Detecting White-Collar Cyber-Crime\", \"time\": \"16:00\", \"ts\": 1687968000, \"speakers\": [\"Yvan Genuer\"]}, {\"title\": \"Jupysec: Auditing Jupyter to Improve AI Security\", \"time\": \"17:00\", \"ts\": 1687971600, \"speakers\": [\"Joe Lucas\"]}]}, {\"title\": \"Active Directory & Azure Security\", \"talks\": [{\"title\": \"Coffee Break\", \"time\": \"10:00\", \"ts\": 1687946400, \"speakers\": []}, {\"title\": \"Dumping NTHashes from Azure AD\", \"time\": \"10:30\", \"ts\": 1687948200, \"speakers\": [\"Nestori Syynimaa\"]}, {\"title\": \"Hidden Pathways: Exploring the Anatomy of ACL-Based Active Directory Attacks and Building Strong Defenses\", \"time\": \"11:30\", \"ts\": 1687951800, \"speakers\": [\"Jonas B\u00fclow Knudsen\", \"Alexander Schmitt\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1687955400, \"speakers\": []}, {\"title\": \"Priority for Effective Action - A Practical Model for quantifying the Risk of Active Directory Attacks\", \"time\": \"13:30\", \"ts\": 1687959000, \"speakers\": [\"Mars Cheng\", \"Dexter Chen\"]}, {\"title\": \"(Windows) Hello from the other side\", \"time\": \"14:30\", \"ts\": 1687962600, \"speakers\": [\"Dirk-jan Mollema\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1687966200, \"speakers\": []}, {\"title\": \"The Power of Coercion Techniques in Windows Environments\", \"time\": \"16:00\", \"ts\": 1687968000, \"speakers\": [\"Martin Grottenthaler\"]}, {\"title\": \"So You Performed A Forest Recovery. How Do You Reconnect Your AD Again With Azure AD?\", \"time\": \"17:00\", \"ts\": 1687971600, \"speakers\": [\"Jorge de Almeida Pinto\"]}]}]}"; -static const char* DEFAULT_DAY2 = "{\"tracks\": [{\"title\": \"Attack & Research\", \"talks\": [{\"title\": \"Testing and Fuzzing the Kubernetes Admission Configuration\", \"time\": \"10:30\", \"ts\": 1688034600, \"speakers\": [\"Benjamin Koltermann\", \"Maximilian Rademacher\"]}, {\"title\": \"Internal Server Error: Exploiting Inter-Process Communication in SAP\u2019s HTTP Server\", \"time\": \"11:30\", \"ts\": 1688038200, \"speakers\": [\"Martin Doyhenard\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1688041800, \"speakers\": []}, {\"title\": \"Monitoring Solutions: Attacking IT Infrastructure at its Core\", \"time\": \"13:30\", \"ts\": 1688045400, \"speakers\": [\"Stefan Schiller\"]}, {\"title\": \"The Anatomy of Windows Telemetry Part 2\", \"time\": \"14:30\", \"ts\": 1688049000, \"speakers\": [\"Tillmann O\u00dfwald\", \"Dominik Phillips\", \"Maximilian Winkler\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1688052600, \"speakers\": []}, {\"title\": \"Everyone knows SAP, everyone uses SAP, everyone uses RFC, no one knows RFC: From RFC to RCE 16 years later\", \"time\": \"16:00\", \"ts\": 1688054400, \"speakers\": [\"Fabian Hagg\"]}, {\"title\": \"Closing\", \"time\": \"17:00\", \"ts\": 1688058000, \"speakers\": []}]}, {\"title\": \"Defense & Management\", \"talks\": [{\"title\": \"OAuth and Proof of Possession - The long way round\", \"time\": \"10:30\", \"ts\": 1688034600, \"speakers\": [\"Dominick Baier\"]}, {\"title\": \"Detection And Blocking With BPF Via YAML\", \"time\": \"11:30\", \"ts\": 1688038200, \"speakers\": [\"Kev Sheldrake\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1688041800, \"speakers\": []}, {\"title\": \"GPT-like Pre-Training on Unlabeled System Logs for Malware Detection\", \"time\": \"13:30\", \"ts\": 1688045400, \"speakers\": [\"Dmitrijs Trizna\", \"Luca Demetrio\"]}, {\"title\": \"Forensic analysis on real incidents inside Microsoft Remote Desktop Services\", \"time\": \"14:30\", \"ts\": 1688049000, \"speakers\": [\"Catarina de Faria Cristas\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1688052600, \"speakers\": []}, {\"title\": \"Reportly - keep your head in the clouds. A new Azure visualization tool for analyzing user activities.\", \"time\": \"16:00\", \"ts\": 1688054400, \"speakers\": [\"Sapir Federovsky\"]}, {\"title\": \"Homophonic Collisions: Hold me closer Tony Danza\", \"time\": \"16:30\", \"ts\": 1688056200, \"speakers\": [\"Justin Ibarra\", \"Reagan Short\"]}]}, {\"title\": \"Track 3\", \"talks\": [{\"title\": \"Horror Stories from the Automotive Industry\", \"time\": \"10:30\", \"ts\": 1688034600, \"speakers\": [\"Thomas Sermpinis\"]}, {\"title\": \"The Wire on Fire: The Spies Who Loved Telcos\", \"time\": \"11:30\", \"ts\": 1688038200, \"speakers\": [\"Aleksandar Milenkoski\"]}, {\"title\": \"Lunch Break\", \"time\": \"12:30\", \"ts\": 1688041800, \"speakers\": []}, {\"title\": \"Fault Injection Attacks on Secure Automotive Bootloaders\", \"time\": \"13:30\", \"ts\": 1688045400, \"speakers\": [\"Nils Weiss\", \"Enrico Pozzobon\"]}, {\"title\": \"Vulnerabilities in the TPM 2.0 reference implementation code\", \"time\": \"14:30\", \"ts\": 1688049000, \"speakers\": [\"Francisco Falcon\"]}, {\"title\": \"Coffee Break\", \"time\": \"15:30\", \"ts\": 1688052600, \"speakers\": []}, {\"title\": \"Beyond Java: Obfuscating Android Apps with Purely Native Code\", \"time\": \"16:00\", \"ts\": 1688054400, \"speakers\": [\"Laurie Kirk\"]}]}]}"; +static const char* DEFAULT_MY = "{\"day1\": [], \"day2\": []}"; +static const char* DEFAULT_DAY1 = "{\"tracks\": [{\"title\": \"Attack & Research\", \"talks\": [{\"id\": 1141, \"title\": \"Keynote\", \"time\": \"09:00\", \"start\": 32400, \"end\": \"10:00\", \"duration\": 3600, \"special\": false, \"ts\": 1687942800, \"speakers\": [\"Mikko Hypp\\u00f6nen\"]}, {\"id\": 1126, \"title\": \"Coffee Break\", \"time\": \"10:00\", \"start\": 36000, \"end\": \"10:30\", \"duration\": 1800, \"special\": true, \"ts\": 1687946400, \"speakers\": []}, {\"id\": 1206, \"title\": \"OopsSec - The bad, the worst and the ugly of APT\\u2019s operations security\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687948200, \"speakers\": [\"Tomer Bar\"]}, {\"id\": 1146, \"title\": \"Spooky authentication at a distance\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687951800, \"speakers\": [\"Tamas Jos\"]}, {\"id\": 1136, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:00\", \"duration\": 1800, \"special\": true, \"ts\": 1687955400, \"speakers\": []}, {\"id\": 1283, \"title\": \"Attacking Ultra-Wideband: Security Analysis of UWB Applications in Smartphones\", \"time\": \"13:45\", \"start\": 49500, \"end\": \"14:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687959900, \"speakers\": [\"Alexander Heinrich\", \"Jiska Classen\"]}, {\"id\": 1181, \"title\": \"Fact Based Post Exploitation - Office365 Edition\", \"time\": \"14:45\", \"start\": 53100, \"end\": \"15:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687963500, \"speakers\": [\"Melvin Langvik\"]}, {\"id\": 1125, \"title\": \"Coffee Break\", \"time\": \"15:45\", \"start\": 56700, \"end\": \"16:15\", \"duration\": 1800, \"special\": true, \"ts\": 1687967100, \"speakers\": []}, {\"id\": 1288, \"title\": \"Forensic Examination of Ceph\", \"time\": \"16:15\", \"start\": 58500, \"end\": \"17:15\", \"duration\": 3600, \"special\": false, \"ts\": 1687968900, \"speakers\": [\"Florian Bausch\"]}, {\"id\": 1290, \"title\": \"All your parcel are belong to us\", \"time\": \"17:15\", \"start\": 62100, \"end\": \"17:45\", \"duration\": 1800, \"special\": false, \"ts\": 1687972500, \"speakers\": [\"Dennis Kniel\", \"Klaus Kiehne\"]}, {\"id\": 1211, \"title\": \"Stay fit: Hack a Jump Rope\", \"time\": \"17:45\", \"start\": 63900, \"end\": \"18:15\", \"duration\": 1800, \"special\": false, \"ts\": 1687974300, \"speakers\": [\"Axelle Apvrille\"]}]}, {\"title\": \"Defense & Management\", \"talks\": [{\"id\": 1129, \"title\": \"Coffee Break\", \"time\": \"10:00\", \"start\": 36000, \"end\": \"10:30\", \"duration\": 1800, \"special\": true, \"ts\": 1687946400, \"speakers\": []}, {\"id\": 1249, \"title\": \"Das IT-Security-Lagebild aus Heise-Sicht\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687948200, \"speakers\": [\"J\\u00fcrgen Schmidt aka ju\"]}, {\"id\": 1173, \"title\": \"Cat & Mouse - or chess?\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687951800, \"speakers\": [\"Fabian Mosch\"]}, {\"id\": 1137, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:30\", \"duration\": 3600, \"special\": true, \"ts\": 1687955400, \"speakers\": []}, {\"id\": 1197, \"title\": \"Real world detection engineering in a multi-cloud environment\", \"time\": \"13:45\", \"start\": 49500, \"end\": \"14:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687959900, \"speakers\": [\"Aaron Jewitt\"]}, {\"id\": 1246, \"title\": \"Security Heroes versus the Power of Privacy\", \"time\": \"14:45\", \"start\": 53100, \"end\": \"15:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687963500, \"speakers\": [\"Avi D\", \"Kim Wuyts\"]}, {\"id\": 1128, \"title\": \"Coffee Break\", \"time\": \"15:45\", \"start\": 56700, \"end\": \"16:15\", \"duration\": 1800, \"special\": true, \"ts\": 1687967100, \"speakers\": []}, {\"id\": 1236, \"title\": \"SAP (Anti-)Forensics: Detecting White-Collar Cyber-Crime\", \"time\": \"16:15\", \"start\": 58500, \"end\": \"17:15\", \"duration\": 3600, \"special\": false, \"ts\": 1687968900, \"speakers\": [\"Yvan Genuer\"]}, {\"id\": 1171, \"title\": \"Jupysec: Auditing Jupyter to Improve AI Security\", \"time\": \"17:15\", \"start\": 62100, \"end\": \"18:15\", \"duration\": 3600, \"special\": false, \"ts\": 1687972500, \"speakers\": [\"Joe Lucas\"]}]}, {\"title\": \"Active Directory & Azure Security\", \"talks\": [{\"id\": 1132, \"title\": \"Coffee Break\", \"time\": \"10:00\", \"start\": 36000, \"end\": \"10:30\", \"duration\": 1800, \"special\": true, \"ts\": 1687946400, \"speakers\": []}, {\"id\": 1278, \"title\": \"Dumping NTHashes from Azure AD\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687948200, \"speakers\": [\"Nestori Syynimaa\"]}, {\"id\": 1213, \"title\": \"Hidden Pathways: Exploring the Anatomy of ACL-Based Active Directory Attacks and Building Strong Defenses\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1687951800, \"speakers\": [\"Jonas B\\u00fclow Knudsen\", \"Alexander Schmitt\"]}, {\"id\": 1140, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:30\", \"duration\": 3600, \"special\": true, \"ts\": 1687955400, \"speakers\": []}, {\"id\": 1224, \"title\": \"Priority for Effective Action - A Practical Model for quantifying the Risk of Active Directory Attacks\", \"time\": \"13:45\", \"start\": 49500, \"end\": \"14:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687959900, \"speakers\": [\"Mars Cheng\", \"Dexter Chen\"]}, {\"id\": 1210, \"title\": \"(Windows) Hello from the other side\", \"time\": \"14:45\", \"start\": 53100, \"end\": \"15:45\", \"duration\": 3600, \"special\": false, \"ts\": 1687963500, \"speakers\": [\"Dirk-jan Mollema\"]}, {\"id\": 1134, \"title\": \"Coffee Break\", \"time\": \"15:45\", \"start\": 56700, \"end\": \"16:15\", \"duration\": 1800, \"special\": true, \"ts\": 1687967100, \"speakers\": []}, {\"id\": 1229, \"title\": \"The Power of Coercion Techniques in Windows Environments\", \"time\": \"16:15\", \"start\": 58500, \"end\": \"17:15\", \"duration\": 3600, \"special\": false, \"ts\": 1687968900, \"speakers\": [\"Martin Grottenthaler\"]}, {\"id\": 1239, \"title\": \"So You Performed A Forest Recovery. How Do You Reconnect Your AD Again With Azure AD?\", \"time\": \"17:15\", \"start\": 62100, \"end\": \"18:15\", \"duration\": 3600, \"special\": false, \"ts\": 1687972500, \"speakers\": [\"Jorge de Almeida Pinto\"]}]}]}"; +static const char* DEFAULT_DAY2 = "{\"tracks\": [{\"title\": \"Attack & Research\", \"talks\": [{\"id\": 1281, \"title\": \"Testing and Fuzzing the Kubernetes Admission Configuration\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688034600, \"speakers\": [\"Benjamin Koltermann\", \"Maximilian Rademacher\"]}, {\"id\": 1299, \"title\": \"Surprise Talk\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688038200, \"speakers\": []}, {\"id\": 1135, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:30\", \"duration\": 3600, \"special\": true, \"ts\": 1688041800, \"speakers\": []}, {\"id\": 1196, \"title\": \"Monitoring Solutions: Attacking IT Infrastructure at its Core\", \"time\": \"13:30\", \"start\": 48600, \"end\": \"14:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688045400, \"speakers\": [\"Stefan Schiller\"]}, {\"id\": 1297, \"title\": \"The Anatomy of Windows Telemetry Part 2\", \"time\": \"14:30\", \"start\": 52200, \"end\": \"15:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688049000, \"speakers\": [\"Tillmann O\\u00dfwald\", \"Dominik Phillips\", \"Maximilian Winkler\"]}, {\"id\": 1124, \"title\": \"Coffee Break\", \"time\": \"15:30\", \"start\": 55800, \"end\": \"16:00\", \"duration\": 1800, \"special\": true, \"ts\": 1688052600, \"speakers\": []}, {\"id\": 1243, \"title\": \"Everyone knows SAP, everyone uses SAP, everyone uses RFC, no one knows RFC: From RFC to RCE 16 years later\", \"time\": \"16:00\", \"start\": 57600, \"end\": \"17:00\", \"duration\": 3600, \"special\": false, \"ts\": 1688054400, \"speakers\": [\"Fabian Hagg\"]}, {\"id\": 1298, \"title\": \"Closing\", \"time\": \"17:00\", \"start\": 61200, \"end\": \"18:00\", \"duration\": 3600, \"special\": false, \"ts\": 1688058000, \"speakers\": []}]}, {\"title\": \"Defense & Management\", \"talks\": [{\"id\": 1225, \"title\": \"OAuth and Proof of Possession - The long way round\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688034600, \"speakers\": [\"Dominick Baier\"]}, {\"id\": 1273, \"title\": \"Detection And Blocking With BPF Via YAML\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688038200, \"speakers\": [\"Kev Sheldrake\"]}, {\"id\": 1138, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:30\", \"duration\": 3600, \"special\": true, \"ts\": 1688041800, \"speakers\": []}, {\"id\": 1212, \"title\": \"GPT-like Pre-Training on Unlabeled System Logs for Malware Detection\", \"time\": \"13:30\", \"start\": 48600, \"end\": \"14:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688045400, \"speakers\": [\"Dmitrijs Trizna\", \"Luca Demetrio\"]}, {\"id\": 1272, \"title\": \"Forensic analysis on real incidents inside Microsoft Remote Desktop Services\", \"time\": \"14:30\", \"start\": 52200, \"end\": \"15:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688049000, \"speakers\": [\"Catarina de Faria Cristas\"]}, {\"id\": 1127, \"title\": \"Coffee Break\", \"time\": \"15:30\", \"start\": 55800, \"end\": \"16:00\", \"duration\": 1800, \"special\": true, \"ts\": 1688052600, \"speakers\": []}, {\"id\": 1147, \"title\": \"Reportly - keep your head in the clouds. A new Azure visualization tool for analyzing user activities.\", \"time\": \"16:00\", \"start\": 57600, \"end\": \"16:30\", \"duration\": 1800, \"special\": false, \"ts\": 1688054400, \"speakers\": [\"Sapir Federovsky\"]}, {\"id\": 1254, \"title\": \"Homophonic Collisions: Hold me closer Tony Danza\", \"time\": \"16:30\", \"start\": 59400, \"end\": \"17:00\", \"duration\": 1800, \"special\": false, \"ts\": 1688056200, \"speakers\": [\"Justin Ibarra\", \"Reagan Short\"]}]}, {\"title\": \"Track 3\", \"talks\": [{\"id\": 1223, \"title\": \"Horror Stories from the Automotive Industry\", \"time\": \"10:30\", \"start\": 37800, \"end\": \"11:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688034600, \"speakers\": [\"Thomas Sermpinis\"]}, {\"id\": 1280, \"title\": \"Beyond Java: Obfuscating Android Apps with Purely Native Code\", \"time\": \"11:30\", \"start\": 41400, \"end\": \"12:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688038200, \"speakers\": [\"Laurie Kirk\"]}, {\"id\": 1139, \"title\": \"Lunch Break\", \"time\": \"12:30\", \"start\": 45000, \"end\": \"13:30\", \"duration\": 3600, \"special\": true, \"ts\": 1688041800, \"speakers\": []}, {\"id\": 1217, \"title\": \"Fault Injection Attacks on Secure Automotive Bootloaders\", \"time\": \"13:30\", \"start\": 48600, \"end\": \"14:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688045400, \"speakers\": [\"Nils Weiss\", \"Enrico Pozzobon\"]}, {\"id\": 1279, \"title\": \"Vulnerabilities in the TPM 2.0 reference implementation code\", \"time\": \"14:30\", \"start\": 52200, \"end\": \"15:30\", \"duration\": 3600, \"special\": false, \"ts\": 1688049000, \"speakers\": [\"Francisco Falcon\"]}, {\"id\": 1131, \"title\": \"Coffee Break\", \"time\": \"15:30\", \"start\": 55800, \"end\": \"16:00\", \"duration\": 1800, \"special\": true, \"ts\": 1688052600, \"speakers\": []}, {\"id\": 1248, \"title\": \"The Wire on Fire: The Spies Who Loved Telcos\", \"time\": \"16:00\", \"start\": 57600, \"end\": \"17:00\", \"duration\": 3600, \"special\": false, \"ts\": 1688054400, \"speakers\": [\"Aleksandar Milenkoski\"]}, {\"id\": 1300, \"title\": \"TR23 Badge - A RETROspective\", \"time\": \"16:30\", \"start\": 59400, \"end\": \"17:00\", \"duration\": 1800, \"special\": false, \"ts\": 1688056200, \"speakers\": [\"Malte Heinzelmann\", \"Jeff \\\"jeffmakes\\\" Gough\"]}]}]}"; static const char* DEFAULT_LAST_UPDATE = "1687853280"; @@ -44,9 +48,13 @@ extern const uint8_t agenda_png_end[] asm("_binary_calendar_png_end"); extern const uint8_t clock_png_start[] asm("_binary_clock_png_start"); extern const uint8_t clock_png_end[] asm("_binary_clock_png_end"); +extern const uint8_t bookmark_png_start[] asm("_binary_bookmark_png_start"); +extern const uint8_t bookmark_png_end[] asm("_binary_bookmark_png_end"); + typedef enum action { ACTION_NONE, ACTION_NEXT_UP, + ACTION_MY_AGENDA, ACTION_WEDNESDAY, ACTION_THURSDAY } menu_agenda_action_t; @@ -60,6 +68,12 @@ static char* data_day2 = NULL; static size_t size_day2 = 0; static cJSON* json_day2 = NULL; +static char* data_my = NULL; +static size_t size_my = 0; +static cJSON* json_my = NULL; +static cJSON* json_my_day1 = NULL; +static cJSON* json_my_day2 = NULL; + static inline void do_init() { FILE* last_update_fd = fopen(last_update_path, "r"); if (last_update_fd != NULL) { @@ -90,6 +104,14 @@ static inline void do_init() { } fwrite(DEFAULT_LAST_UPDATE, 1, strlen(DEFAULT_LAST_UPDATE), new_last_update_fd); fclose(new_last_update_fd); + + FILE* my_fd = fopen(my_agenda_path, "w"); + if (my_fd == NULL) { + ESP_LOGE(TAG, "Cannot init agenda last_updated."); + return; + } + fwrite(DEFAULT_MY, 1, strlen(DEFAULT_MY), my_fd); + fclose(my_fd); ESP_LOGI(TAG, "Successfully written initial agenda data"); } @@ -98,18 +120,20 @@ void agenda_render_background(pax_buf_t* pax_buffer) { pax_background(pax_buffer, 0xFF1E1E1E); pax_noclip(pax_buffer); pax_simple_rect(pax_buffer, 0xff131313, 0, 220, 320, 20); - pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅰 accept 🅱 back"); + pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅰 Accept 🅱 Exit"); } -void details_render_background(pax_buf_t* pax_buffer, bool tracks) { +void details_render_background(pax_buf_t* pax_buffer, bool tracks, bool days) { const pax_font_t* font = pax_font_saira_regular; pax_background(pax_buffer, 0xFF1E1E1E); pax_noclip(pax_buffer); pax_simple_rect(pax_buffer, 0xff131313, 0, 220, 320, 20); if (tracks) { - pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅱 back ← → change track"); + pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅱 Back ← → Track 🅴 Bookmark"); + } else if (days) { + pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅱 Back ← → Day 🅴 Bookmark"); } else { - pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅱 back"); + pax_draw_text(pax_buffer, 0xffffffff, font, 18, 5, 240 - 18, "🅱 Back 🅴 Bookmark"); } } @@ -120,20 +144,26 @@ void render_topbar(pax_buf_t* pax_buffer, pax_buf_t* icon, const char* text) { pax_draw_text(pax_buffer, 0xFFF1AA13, font, 18, 34, 8, text); } -int render_track(pax_buf_t* pax_buffer, pax_buf_t* icon, cJSON* tracks, int track, int talk, int render_talks, int slot_height) { +int render_track(pax_buf_t* pax_buffer, pax_buf_t* icon_top, pax_buf_t* icon_bookmarked, cJSON* tracks, int track, int talk, int render_talks, int slot_height) { const pax_font_t* font = pax_font_saira_regular; cJSON* track_data = cJSON_GetArrayItem(tracks, track); cJSON* title = cJSON_GetObjectItem(track_data, "title"); cJSON* talks = cJSON_GetObjectItem(track_data, "talks"); + cJSON* talk_data; + cJSON* time; + cJSON* talk_title; + cJSON* speakers; + cJSON* speaker; + int talk_count = cJSON_GetArraySize(talks); if (talk >= talk_count) { talk = talk_count - 1; } - details_render_background(pax_buffer, true); - render_topbar(pax_buffer, icon, title->valuestring); + details_render_background(pax_buffer, true, false); + render_topbar(pax_buffer, icon_top, title->valuestring); // First talk int start_talk = talk; @@ -153,12 +183,102 @@ int render_track(pax_buf_t* pax_buffer, pax_buf_t* icon, cJSON* tracks, int trac pax_col_t fg_color = (current == talk) ? 0xFF131313 : 0xFFF1AA13; pax_simple_rect(pax_buffer, bg_color, 0, y, 320, slot_height); - cJSON* talk_data = cJSON_GetArrayItem(talks, current); - cJSON* time = cJSON_GetObjectItem(talk_data, "time"); - cJSON* talk_title = cJSON_GetObjectItem(talk_data, "title"); - cJSON* speakers = cJSON_GetObjectItem(talk_data, "speakers"); + talk_data = cJSON_GetArrayItem(talks, current); + time = cJSON_GetObjectItem(talk_data, "time"); + talk_title = cJSON_GetObjectItem(talk_data, "title"); + speakers = cJSON_GetObjectItem(talk_data, "speakers"); pax_draw_text(pax_buffer, fg_color, font, 14, 5, y + 2, time->valuestring); + if (cJSON_IsTrue(cJSON_GetObjectItem(talk_data, BOOKMARKED))) { + pax_draw_image_sized(pax_buffer, icon_bookmarked, 302, y + 1, 16, 16); + } + + pax_draw_text(pax_buffer, fg_color, font, 18, 5, y + 2 + 18, talk_title->valuestring); + + int max_len = 255; + char speaker_str[max_len]; + speaker_str[max_len-1] = 0; + uint offset = 1; + for (int a = 0; a < cJSON_GetArraySize(speakers); a++) { + speaker = cJSON_GetArrayItem(speakers, a); + uint len = strlen(speaker->valuestring); + strncat(speaker_str, speaker->valuestring, max_len - 1 - offset); + offset += len; + if (a < cJSON_GetArraySize(speakers) - 1) { + strncat(speaker_str, ", ", max_len - 1 - offset); + offset += 2; + } + if (offset >= max_len - 2) { + break; + } + } + + pax_draw_text(pax_buffer, fg_color, font, 14, 15, y + 2 + 18 + 20, speaker_str); + } + + display_flush(); + return talk_count; +} + +int render_bookmarks(pax_buf_t* pax_buffer, pax_buf_t* icon, cJSON* bookmarks, int day, int talk, int render_talks, int slot_height) { + const pax_font_t* font = pax_font_saira_regular; + + int talk_count = cJSON_GetArraySize(bookmarks); + if (talk >= talk_count) { + talk = talk_count - 1; + } + + details_render_background(pax_buffer, false, true); + if (day == 0) { + render_topbar(pax_buffer, icon, "Bookmarks - Wednesday"); + } else { + render_topbar(pax_buffer, icon, "Bookmarks - Thursday"); + } + + // First talk + int start_talk = talk; + int end_talk = talk + render_talks - 1; + if (end_talk >= talk_count) { + start_talk -= end_talk - talk_count + 1; + end_talk = talk_count - 1; + } + if (start_talk < 0) { + start_talk = 0; + } + + cJSON* bookmark; + cJSON* track_data; + cJSON* talk_data; + cJSON* time; + cJSON* track; + cJSON* talk_title; + cJSON* speakers; + + for (int current = start_talk; current <= end_talk; current++) { + int i = current - start_talk; + int y = i * slot_height + 34; + pax_col_t bg_color = (current == talk) ? 0xFFF1AA13 : 0xFF131313; + pax_col_t fg_color = (current == talk) ? 0xFF131313 : 0xFFF1AA13; + pax_simple_rect(pax_buffer, bg_color, 0, y, 320, slot_height); + + bookmark = cJSON_GetArrayItem(bookmarks, current); + track_data = cJSON_GetObjectItem(bookmark, "track"); + talk_data = cJSON_GetObjectItem(bookmark, "talk"); + + track = cJSON_GetObjectItem(track_data, "title"); + time = cJSON_GetObjectItem(talk_data, "time"); + talk_title = cJSON_GetObjectItem(talk_data, "title"); + speakers = cJSON_GetObjectItem(talk_data, "speakers"); + + uint len_time = strlen(time->valuestring); + uint len_track = strlen(track->valuestring); + char* time_title = malloc(len_time + len_track + 2); + memcpy(time_title, time->valuestring, len_time); + time_title[len_time] = ' '; + memcpy(time_title + len_time + 1, track->valuestring, len_track); + time_title[len_time + len_track + 1] = 0; + pax_draw_text(pax_buffer, fg_color, font, 14, 5, y + 2, time_title); + free(time_title); pax_draw_text(pax_buffer, fg_color, font, 18, 5, y + 2 + 18, talk_title->valuestring); @@ -187,7 +307,87 @@ int render_track(pax_buf_t* pax_buffer, pax_buf_t* icon, cJSON* tracks, int trac return talk_count; } -void details_day(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* data, pax_buf_t* icon) { +bool save_bookmarks() { + char* json_string = cJSON_PrintUnformatted(json_my); + if (json_string == NULL) { + ESP_LOGE(TAG, "Cannot serialize bookmarks"); + return false; + } + + printf("%s\n", json_string); + + FILE* my_fd = fopen(my_agenda_path, "w"); + if (my_fd == NULL) { + ESP_LOGE(TAG, "Cannot init agenda last_updated."); + return false; + } + fwrite(json_string, 1, strlen(json_string), my_fd); + fclose(my_fd); + return true; +} + +bool toggle_bookmark(cJSON* track, cJSON* talk, cJSON* my_day, cJSON* bookmarks) { + if (talk == NULL) { + ESP_LOGW(TAG, "Cannot toggle bookmark if no talk is given"); + return false; + } + bool added = false; + + if (!cJSON_HasObjectItem(talk, BOOKMARKED)) { + added = true; + cJSON_AddItemToObject(talk, BOOKMARKED, cJSON_CreateBool(true)); + } else { + cJSON* bookmarked = cJSON_GetObjectItem(talk, BOOKMARKED); + added = cJSON_IsFalse(bookmarked); + cJSON_SetBoolValue(bookmarked, added); + } + + int bookmarks_count = cJSON_GetArraySize(bookmarks); + long id = (long) cJSON_GetNumberValue(cJSON_GetObjectItem(talk, "id")); + long start = (long) cJSON_GetNumberValue(cJSON_GetObjectItem(talk, "start")); + + ESP_LOGD(TAG, "%s", cJSON_PrintUnformatted(bookmarks)); + ESP_LOGD(TAG, "%s", cJSON_PrintUnformatted(my_day)); + + if (added) { + cJSON* entry_id = cJSON_CreateNumber(id); + cJSON* entry_list = cJSON_CreateObject(); + if (entry_list == NULL) { + ESP_LOGE(TAG, "Failed to create bookmark entry for %ld", id); + return false; + } + + cJSON_AddItemReferenceToObject(entry_list, "track", track); + cJSON_AddItemReferenceToObject(entry_list, "talk", talk); + // Add to the list at correct position, expect the current list to be sorted + cJSON* next = cJSON_GetArrayItem(bookmarks, 0); + int i = 0; + while(next != NULL && cJSON_GetNumberValue(cJSON_GetObjectItem(cJSON_GetObjectItem(next, "talk"), "start")) <= start) { + i++; + next = next->next; + } + + cJSON_InsertItemInArray(my_day, i, entry_id); + cJSON_InsertItemInArray(bookmarks, i, entry_list); + } else { + // Remove from the correct position + cJSON* item; + for (int i = 0; i < bookmarks_count; i++) { + item = cJSON_GetArrayItem(bookmarks, i); + if ((long) cJSON_GetNumberValue(cJSON_GetObjectItem(cJSON_GetObjectItem(item, "talk"), "id")) == id) { + cJSON_DeleteItemFromArray(bookmarks, i); + cJSON_DeleteItemFromArray(my_day, i); + break; + } + } + } + + save_bookmarks(); + + return true; +} + +void details_day(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* data, cJSON* my_day, cJSON* bookmarks, pax_buf_t* icon_top, pax_buf_t* icon_bookmarked) { int track = 0; cJSON* tracks = cJSON_GetObjectItem(data, "tracks"); int track_count = cJSON_GetArraySize(tracks); @@ -207,9 +407,12 @@ void details_day(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* data, int talk_count; keyboard_input_message_t buttonMessage = {0}; + cJSON* talks = cJSON_GetObjectItem(cJSON_GetArrayItem(tracks, track), "talks"); + cJSON* talk_data; + while(!exit) { if (render) { - talk_count = render_track(pax_buffer, icon, tracks, track, talk, render_talks, slot_height); + talk_count = render_track(pax_buffer, icon_top, icon_bookmarked, tracks, track, talk, render_talks, slot_height); render = false; } @@ -240,6 +443,16 @@ void details_day(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* data, case BUTTON_BACK: exit = true; break; + case BUTTON_SELECT: + case JOYSTICK_PUSH: + talk_data = cJSON_GetArrayItem(talks, talk); + if (cJSON_IsTrue(cJSON_GetObjectItem(talk_data, "special"))) { + // Don't allow adding breaks to bookmarks + break; + } + toggle_bookmark(cJSON_GetArrayItem(tracks, track), talk_data, my_day, bookmarks); + render = true; + break; default: break; } @@ -248,6 +461,79 @@ void details_day(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* data, } } +void my_agenda(pax_buf_t* pax_buffer, xQueueHandle button_queue, cJSON* day1, cJSON* day2, pax_buf_t* icon) { + if (day1 == NULL || day2 == NULL) { + render_message("No talks found"); + display_flush(); + wait_for_button(); + return; + } + + bool render = true; + bool exit = false; + + int days = 2; + int day = 0; + int talk = 0; + int render_talks = 3; + int slot_height = 62; + int talk_count; + keyboard_input_message_t buttonMessage = {0}; + + + cJSON* data = day1; + cJSON* talk_data; + cJSON* my_day; + cJSON* bookmarks; + + while(!exit) { + if (render) { + talk_count = render_bookmarks(pax_buffer, icon, data, day, talk, render_talks, slot_height); + render = false; + } + + clear_keyboard_queue(); + if (xQueueReceive(button_queue, &buttonMessage, portMAX_DELAY) == pdTRUE) { + if (buttonMessage.state) { + switch (buttonMessage.input) { + case JOYSTICK_DOWN: + talk = (talk + 1) % talk_count; + render = true; + break; + case JOYSTICK_UP: + talk = (talk - 1 + talk_count) % talk_count; + render = true; + break; + case JOYSTICK_LEFT: + day = (day - 1 + days) % days; + data = day == 0 ? day1 : day2; + render = true; + break; + case JOYSTICK_RIGHT: + day = (day + 1) % days; + data = day == 0 ? day1 : day2; + render = true; + break; + case BUTTON_SELECT: + case JOYSTICK_PUSH: + talk_data = cJSON_GetArrayItem(data, talk % talk_count); + my_day = cJSON_GetObjectItem(json_my, (day == 0) ? "day1" : "day2"); + bookmarks = (day == 0) ? json_my_day1 : json_my_day2; + toggle_bookmark(cJSON_GetObjectItem(talk_data, "track"), cJSON_GetObjectItem(talk_data, "talk"), my_day, bookmarks); + render = true; + break; + case BUTTON_BACK: + exit = true; + break; + default: + break; + } + } + } + } +} + + void details_upcoming(pax_buf_t* pax_buffer, cJSON* data, pax_buf_t* icon) { const pax_font_t* font = pax_font_saira_regular; @@ -272,7 +558,7 @@ void details_upcoming(pax_buf_t* pax_buffer, cJSON* data, pax_buf_t* icon) { time(&now); localtime_r(&now, &timeinfo); - details_render_background(pax_buffer, false); + details_render_background(pax_buffer, false, false); render_topbar(pax_buffer, icon, "Upcoming talks"); // Remaining vertical space is 186px -> 62px per track @@ -360,8 +646,8 @@ bool need_update(unsigned long *remote_last_update) { memcpy(buf2, remote_buf, 10); *remote_last_update = atol(buf2); - ESP_LOGI(TAG, "local=%lu, remote=%lu", local_last_update, *remote_last_update); - ESP_LOGI(TAG, "local=%s, remote=%s", buf, buf2); + ESP_LOGD(TAG, "local=%lu, remote=%lu", local_last_update, *remote_last_update); + ESP_LOGD(TAG, "local=%s, remote=%s", buf, buf2); return *remote_last_update > local_last_update; } @@ -428,6 +714,84 @@ bool test_load_data() { return true; } +uint find_correct_position(cJSON* my_day, long start) { + cJSON* next = cJSON_GetArrayItem(my_day, 0); + int i = 0; + while(next != NULL && cJSON_GetNumberValue(cJSON_GetObjectItem(cJSON_GetObjectItem(next, "talk"), "start")) <= start) { + i++; + next = next->next; + } + return i; +} + +bool load_my_data(cJSON* day, cJSON* bookmarks, cJSON* results) { + cJSON *bookmark; + + int bookmark_count = cJSON_GetArraySize(bookmarks); + + cJSON* tracks = cJSON_GetObjectItem(day, "tracks"); + if (tracks == NULL) { + ESP_LOGE(TAG, "No tracks found"); + return false; + } + int tracks_count = cJSON_GetArraySize(tracks); + int talks_count; + + cJSON* track; + cJSON* talks; + cJSON* talk; + + bool found = false; + long searchId; + long currentId; + cJSON* result; + + for (int i = 0; i < bookmark_count; i++) { + bookmark = cJSON_GetArrayItem(bookmarks, i); + searchId = (long) cJSON_GetNumberValue(bookmark); + found = false; + + for (int j = 0; j < tracks_count; j++) { + track = cJSON_GetArrayItem(tracks, j); + talks = cJSON_GetObjectItem(track, "talks"); + talks_count = cJSON_GetArraySize(talks); + + for (int k = 0; k < talks_count; k++) { + talk = cJSON_GetArrayItem(talks, k); + currentId = (long) cJSON_GetNumberValue(cJSON_GetObjectItem(talk, "id")); + if (searchId == currentId) { + // Found the right talk, add it to the result array + found = true; + result = cJSON_CreateObject(); + if (result == NULL) { + ESP_LOGE(TAG, "Failed to create bookmark entry for %ld", searchId); + return false; + } + + cJSON_AddItemReferenceToObject(result, "track", track); + cJSON_AddItemReferenceToObject(result, "talk", talk); + + // Set bookmarked + if (!cJSON_HasObjectItem(talk, BOOKMARKED)) { + cJSON_AddItemToObject(talk, BOOKMARKED, cJSON_CreateBool(true)); + } else { + cJSON_SetBoolValue(cJSON_GetObjectItem(talk, BOOKMARKED), true); + } + + // We sort here as well, bcz talks might be shifted -> IDs are not in correct order anymore + uint pos = find_correct_position(results, (long) cJSON_GetNumberValue(cJSON_GetObjectItem(talk, "start"))); + cJSON_InsertItemInArray(results, pos, result); + } + } + } + + if (!found) { + ESP_LOGW(TAG, "Didn't find a talk for bookmark ID %ld", searchId); + } + } + return true; +} + bool load_data() { if (!load_file(day1_path, &data_day1, &size_day1)) { ESP_LOGE(TAG, "Failed to read agenda file: %s", day1_path); @@ -443,6 +807,16 @@ bool load_data() { return false; } + if (!load_file(my_agenda_path, &data_my, &size_my)) { + ESP_LOGE(TAG, "Failed to read agenda file: %s", my_agenda_path); + render_message("Failed to read agenda. 🅰 to retry."); + display_flush(); + return false; + } + + if (json_day1 != NULL) { + cJSON_Delete(json_day1); + } json_day1 = cJSON_ParseWithLength(data_day1, size_day1); if (json_day1 == NULL) { ESP_LOGE(TAG, "Failed to parse agenda file: %s", day1_path); @@ -451,6 +825,9 @@ bool load_data() { return false; } + if (json_day2 != NULL) { + cJSON_Delete(json_day2); + } json_day2 = cJSON_ParseWithLength(data_day2, size_day2); if (json_day2 == NULL) { ESP_LOGE(TAG, "Failed to parse agenda file: %s", day2_path); @@ -459,6 +836,37 @@ bool load_data() { return false; } + if (json_my != NULL) { + cJSON_Delete(json_my); + } + json_my = cJSON_ParseWithLength(data_my, size_my); + if (json_my == NULL) { + ESP_LOGE(TAG, "Failed to parse agenda file: %s", my_agenda_path); + render_message("Failed to parse agenda. 🅰 to retry."); + display_flush(); + return false; + } + + // A day in json_my contains a list of talk IDs. To print them fast, we look them up here and prepare the list + + json_my_day1 = cJSON_CreateArray(); + if (json_my_day1 == NULL) { + ESP_LOGE(TAG, "Failed to create my_day1"); + render_message("Failed to parse agenda. 🅰 to retry."); + display_flush(); + return false; + } + json_my_day2 = cJSON_CreateArray(); + if (json_my_day2 == NULL) { + ESP_LOGE(TAG, "Failed to create my_day2"); + render_message("Failed to parse agenda. 🅰 to retry."); + display_flush(); + return false; + } + + load_my_data(json_day1, cJSON_GetObjectItem(json_my, "day1"), json_my_day1); + load_my_data(json_day2, cJSON_GetObjectItem(json_my, "day2"), json_my_day2); + return true; } @@ -567,9 +975,8 @@ cJSON* get_current_day() { if (timeinfo.tm_mday == 28) return json_day1; if (timeinfo.tm_mday == 29) return json_day2; - // TODO: remove after testing - return json_day1; -// return NULL; + + return NULL; } bool try_update_or_load(xQueueHandle button_queue, bool first_attempt) { @@ -583,6 +990,7 @@ bool try_update_or_load(xQueueHandle button_queue, bool first_attempt) { return false; } + void menu_agenda(xQueueHandle button_queue) { pax_buf_t* pax_buffer = get_pax_buffer(); @@ -620,7 +1028,7 @@ void menu_agenda(xQueueHandle button_queue) { } } while (1); - menu_t* menu = menu_alloc("TROOPERS23 - Agenda", 34, 18); + menu_t* menu = menu_alloc("TROOPERS24 - Agenda", 34, 18); menu->fgColor = 0xFFF1AA13; menu->bgColor = 0xFF131313; @@ -636,8 +1044,11 @@ void menu_agenda(xQueueHandle button_queue) { pax_decode_png_buf(&icon_agenda, (void*) agenda_png_start, agenda_png_end - agenda_png_start, PAX_BUF_32_8888ARGB, 0); pax_buf_t icon_clock; pax_decode_png_buf(&icon_clock, (void*) clock_png_start, clock_png_end - clock_png_start, PAX_BUF_32_8888ARGB, 0); + pax_buf_t icon_bookmark; + pax_decode_png_buf(&icon_bookmark, (void*) bookmark_png_start, bookmark_png_end - bookmark_png_start, PAX_BUF_32_8888ARGB, 0); menu_set_icon(menu, &icon_agenda); + menu_insert_item_icon(menu, "Bookmarks", NULL, (void*) ACTION_MY_AGENDA, -1, &icon_bookmark); if (ntp_synced) { menu_insert_item_icon(menu, "Next up", NULL, (void*) ACTION_NEXT_UP, -1, &icon_clock); } @@ -706,10 +1117,12 @@ void menu_agenda(xQueueHandle button_queue) { if (action != ACTION_NONE) { if (action == ACTION_NEXT_UP) { details_upcoming(pax_buffer, get_current_day(), &icon_clock); + } else if (action == ACTION_MY_AGENDA) { + my_agenda(pax_buffer, button_queue, json_my_day1, json_my_day2, &icon_bookmark); } else if (action == ACTION_WEDNESDAY) { - details_day(pax_buffer, button_queue, json_day1, &icon_agenda); + details_day(pax_buffer, button_queue, json_day1, cJSON_GetObjectItem(json_my, "day1"), json_my_day1, &icon_agenda, &icon_bookmark); } else if (action == ACTION_THURSDAY) { - details_day(pax_buffer, button_queue, json_day2, &icon_agenda); + details_day(pax_buffer, button_queue, json_day2, cJSON_GetObjectItem(json_my, "day2"), json_my_day2, &icon_agenda, &icon_bookmark); } action = ACTION_NONE; render = true; @@ -718,6 +1131,22 @@ void menu_agenda(xQueueHandle button_queue) { } menu_free(menu); + + cJSON_Delete(json_my_day1); + json_my_day1 = NULL; + + cJSON_Delete(json_my_day2); + json_my_day2 = NULL; + + cJSON_Delete(json_my); + json_my = NULL; + + // Delete the data loaded from JSON + cJSON_Delete(json_day1); + json_day1 = NULL; + cJSON_Delete(json_day2); + json_day2 = NULL; + pax_buf_destroy(&icon_agenda); pax_buf_destroy(&icon_clock); } diff --git a/main/menus/start.c b/main/menus/start.c index 6ac6273..f29b365 100644 --- a/main/menus/start.c +++ b/main/menus/start.c @@ -82,12 +82,15 @@ void render_background(pax_buf_t* pax_buffer, const char* text) { } void menu_start(xQueueHandle button_queue, const char* version, bool wakeup_deepsleep) { + // TODO: Debugging + menu_agenda(button_queue); + if (wakeup_deepsleep) { show_nametag(button_queue); } pax_buf_t* pax_buffer = get_pax_buffer(); - menu_t* menu = menu_alloc("TROOPERS23", 34, 18); + menu_t* menu = menu_alloc("TROOPERS24", 34, 18); menu->fgColor = 0xFFF1AA13; menu->bgColor = 0xFF131313; diff --git a/package.sh b/package.sh index 9c8d3f4..3614134 100755 --- a/package.sh +++ b/package.sh @@ -6,7 +6,7 @@ cp build/bootloader/bootloader.bin ../dist/ cp build/partition_table/partition-table.bin ../dist/ cp build/ota_data_initial.bin ../dist/ cp build/phy_init_data.bin ../dist/ -cp build/TROOPERS23.bin ../dist/ +cp build/TROOPERS24.bin ../dist/ (cd components/appfs/tools/; ./generate.sh) @@ -16,7 +16,7 @@ echo "#!/bin/bash" > ../dist/flash.sh echo "" >> ../dist/flash.sh echo 'DEVICE=$1' >> ../dist/flash.sh echo "esptool.py erase_flash" >> ../dist/flash.sh -echo 'esptool.py -p $DEVICE -b 460800 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x1000 bootloader.bin 0x8000 partition-table.bin 0xd000 ota_data_initial.bin 0xf000 phy_init_data.bin 0x10000 TROOPERS23.bin' >> ../dist/flash.sh +echo 'esptool.py -p $DEVICE -b 460800 --before default_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 80m 0x1000 bootloader.bin 0x8000 partition-table.bin 0xd000 ota_data_initial.bin 0xf000 phy_init_data.bin 0x10000 TROOPERS24.bin' >> ../dist/flash.sh echo 'esptool.py --port $DEVICE --baud 960000 write_flash 0x330000 appfs.bin' >> ../dist/flash.sh chmod +x ../dist/flash.sh diff --git a/resources/icons/bookmark.png b/resources/icons/bookmark.png new file mode 100644 index 0000000000000000000000000000000000000000..3e9b4ec0f1f2b35a06fdbb7eb7d193ba29d635fa GIT binary patch literal 462 zcmV;<0WtoGP)1SKgj%Lov7@sMk0`s0w> zBiG-HbPv04v138^M{&4~!knN7?pq88vi@EuV0?3w$GdiKbys=>MiqMy0yBLD#*G!Q z0#?8ZSOF_w1+0J-umb-Pcn8^1kp%EMUIF3JBfMzJ=_{v{zH5af4m^xQ;7ie*S@~Rz zmQ&B|;$$&hH6u1HiFl1qlnjv;Y7A07*qoM6N<$ Eg5Ytx_W%F@ literal 0 HcmV?d00001 diff --git a/resources/icons/bookmark.svg b/resources/icons/bookmark.svg new file mode 100644 index 0000000..b6e4ba2 --- /dev/null +++ b/resources/icons/bookmark.svg @@ -0,0 +1,39 @@ + + + + + + +