From 51efff55807b68a2e21ca0d48f3be4e5892f6bd0 Mon Sep 17 00:00:00 2001 From: d4rks1d33 Date: Fri, 11 Sep 2026 00:05:06 +0000 Subject: [PATCH] Hi tag go 2 hell --- .../main/subghz/helpers/hell_kernels_gen.h | 506 ++++++++++++++++++ .../main/subghz/helpers/subghz_hitag2_bf.c | 45 +- .../main/subghz/helpers/subghz_hitag2_bf.h | 17 + .../main/subghz/helpers/subghz_hitag2_hell.c | 163 +++--- .../main/subghz/helpers/subghz_hitag2_hell.o | Bin 0 -> 20136 bytes .../subghz/scenes/subghz_scene_hitag2_bf.c | 205 ++++++- 6 files changed, 865 insertions(+), 71 deletions(-) create mode 100644 applications/main/subghz/helpers/hell_kernels_gen.h create mode 100644 applications/main/subghz/helpers/subghz_hitag2_hell.o diff --git a/applications/main/subghz/helpers/hell_kernels_gen.h b/applications/main/subghz/helpers/hell_kernels_gen.h new file mode 100644 index 00000000..619da926 --- /dev/null +++ b/applications/main/subghz/helpers/hell_kernels_gen.h @@ -0,0 +1,506 @@ +// AUTO-GENERATED by gen_hell_kernels.py -- DO NOT EDIT. +// Per-round specialized bitsliced filter/LFSR kernels with +// compile-time-constant state[] indices. Bit-identical to the +// original runtime-index bs_filter_at / bs_lfsr_at. +// +// Requires (before #include): bitslice_t, STATE_ARR_LEN, +// f_a_bs, f_b_bs, f_c_bs. +#ifndef HELL_KERNELS_GEN_H +#define HELL_KERNELS_GEN_H + +// ---- Per-round filter kernels ---- +static inline bitslice_t bs_filter_at_r0(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[45], state[44], state[42], state[41]); + const bitslice_t g1 = f_b_bs(state[39], state[35], state[33], state[32]); + const bitslice_t g2 = f_b_bs(state[30], state[26], state[24], state[21]); + const bitslice_t g3 = f_b_bs(state[19], state[18], state[16], state[14]); + const bitslice_t g4 = f_a_bs(state[13], state[4], state[3], state[1]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r1(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[44], state[43], state[41], state[40]); + const bitslice_t g1 = f_b_bs(state[38], state[34], state[32], state[31]); + const bitslice_t g2 = f_b_bs(state[29], state[25], state[23], state[20]); + const bitslice_t g3 = f_b_bs(state[18], state[17], state[15], state[13]); + const bitslice_t g4 = f_a_bs(state[12], state[3], state[2], state[0]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r2(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[43], state[42], state[40], state[39]); + const bitslice_t g1 = f_b_bs(state[37], state[33], state[31], state[30]); + const bitslice_t g2 = f_b_bs(state[28], state[24], state[22], state[19]); + const bitslice_t g3 = f_b_bs(state[17], state[16], state[14], state[12]); + const bitslice_t g4 = f_a_bs(state[11], state[2], state[1], state[48]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r3(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[42], state[41], state[39], state[38]); + const bitslice_t g1 = f_b_bs(state[36], state[32], state[30], state[29]); + const bitslice_t g2 = f_b_bs(state[27], state[23], state[21], state[18]); + const bitslice_t g3 = f_b_bs(state[16], state[15], state[13], state[11]); + const bitslice_t g4 = f_a_bs(state[10], state[1], state[0], state[49]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r4(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[41], state[40], state[38], state[37]); + const bitslice_t g1 = f_b_bs(state[35], state[31], state[29], state[28]); + const bitslice_t g2 = f_b_bs(state[26], state[22], state[20], state[17]); + const bitslice_t g3 = f_b_bs(state[15], state[14], state[12], state[10]); + const bitslice_t g4 = f_a_bs(state[9], state[0], state[48], state[50]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r5(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[40], state[39], state[37], state[36]); + const bitslice_t g1 = f_b_bs(state[34], state[30], state[28], state[27]); + const bitslice_t g2 = f_b_bs(state[25], state[21], state[19], state[16]); + const bitslice_t g3 = f_b_bs(state[14], state[13], state[11], state[9]); + const bitslice_t g4 = f_a_bs(state[8], state[48], state[49], state[51]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r6(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[39], state[38], state[36], state[35]); + const bitslice_t g1 = f_b_bs(state[33], state[29], state[27], state[26]); + const bitslice_t g2 = f_b_bs(state[24], state[20], state[18], state[15]); + const bitslice_t g3 = f_b_bs(state[13], state[12], state[10], state[8]); + const bitslice_t g4 = f_a_bs(state[7], state[49], state[50], state[52]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r7(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[38], state[37], state[35], state[34]); + const bitslice_t g1 = f_b_bs(state[32], state[28], state[26], state[25]); + const bitslice_t g2 = f_b_bs(state[23], state[19], state[17], state[14]); + const bitslice_t g3 = f_b_bs(state[12], state[11], state[9], state[7]); + const bitslice_t g4 = f_a_bs(state[6], state[50], state[51], state[53]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r8(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[37], state[36], state[34], state[33]); + const bitslice_t g1 = f_b_bs(state[31], state[27], state[25], state[24]); + const bitslice_t g2 = f_b_bs(state[22], state[18], state[16], state[13]); + const bitslice_t g3 = f_b_bs(state[11], state[10], state[8], state[6]); + const bitslice_t g4 = f_a_bs(state[5], state[51], state[52], state[54]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r9(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[36], state[35], state[33], state[32]); + const bitslice_t g1 = f_b_bs(state[30], state[26], state[24], state[23]); + const bitslice_t g2 = f_b_bs(state[21], state[17], state[15], state[12]); + const bitslice_t g3 = f_b_bs(state[10], state[9], state[7], state[5]); + const bitslice_t g4 = f_a_bs(state[4], state[52], state[53], state[55]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r10(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[35], state[34], state[32], state[31]); + const bitslice_t g1 = f_b_bs(state[29], state[25], state[23], state[22]); + const bitslice_t g2 = f_b_bs(state[20], state[16], state[14], state[11]); + const bitslice_t g3 = f_b_bs(state[9], state[8], state[6], state[4]); + const bitslice_t g4 = f_a_bs(state[3], state[53], state[54], state[56]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r11(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[34], state[33], state[31], state[30]); + const bitslice_t g1 = f_b_bs(state[28], state[24], state[22], state[21]); + const bitslice_t g2 = f_b_bs(state[19], state[15], state[13], state[10]); + const bitslice_t g3 = f_b_bs(state[8], state[7], state[5], state[3]); + const bitslice_t g4 = f_a_bs(state[2], state[54], state[55], state[57]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r12(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[33], state[32], state[30], state[29]); + const bitslice_t g1 = f_b_bs(state[27], state[23], state[21], state[20]); + const bitslice_t g2 = f_b_bs(state[18], state[14], state[12], state[9]); + const bitslice_t g3 = f_b_bs(state[7], state[6], state[4], state[2]); + const bitslice_t g4 = f_a_bs(state[1], state[55], state[56], state[58]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r13(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[32], state[31], state[29], state[28]); + const bitslice_t g1 = f_b_bs(state[26], state[22], state[20], state[19]); + const bitslice_t g2 = f_b_bs(state[17], state[13], state[11], state[8]); + const bitslice_t g3 = f_b_bs(state[6], state[5], state[3], state[1]); + const bitslice_t g4 = f_a_bs(state[0], state[56], state[57], state[59]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r14(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[31], state[30], state[28], state[27]); + const bitslice_t g1 = f_b_bs(state[25], state[21], state[19], state[18]); + const bitslice_t g2 = f_b_bs(state[16], state[12], state[10], state[7]); + const bitslice_t g3 = f_b_bs(state[5], state[4], state[2], state[0]); + const bitslice_t g4 = f_a_bs(state[48], state[57], state[58], state[60]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r15(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[30], state[29], state[27], state[26]); + const bitslice_t g1 = f_b_bs(state[24], state[20], state[18], state[17]); + const bitslice_t g2 = f_b_bs(state[15], state[11], state[9], state[6]); + const bitslice_t g3 = f_b_bs(state[4], state[3], state[1], state[48]); + const bitslice_t g4 = f_a_bs(state[49], state[58], state[59], state[61]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r16(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[29], state[28], state[26], state[25]); + const bitslice_t g1 = f_b_bs(state[23], state[19], state[17], state[16]); + const bitslice_t g2 = f_b_bs(state[14], state[10], state[8], state[5]); + const bitslice_t g3 = f_b_bs(state[3], state[2], state[0], state[49]); + const bitslice_t g4 = f_a_bs(state[50], state[59], state[60], state[62]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r17(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[28], state[27], state[25], state[24]); + const bitslice_t g1 = f_b_bs(state[22], state[18], state[16], state[15]); + const bitslice_t g2 = f_b_bs(state[13], state[9], state[7], state[4]); + const bitslice_t g3 = f_b_bs(state[2], state[1], state[48], state[50]); + const bitslice_t g4 = f_a_bs(state[51], state[60], state[61], state[63]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r18(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[27], state[26], state[24], state[23]); + const bitslice_t g1 = f_b_bs(state[21], state[17], state[15], state[14]); + const bitslice_t g2 = f_b_bs(state[12], state[8], state[6], state[3]); + const bitslice_t g3 = f_b_bs(state[1], state[0], state[49], state[51]); + const bitslice_t g4 = f_a_bs(state[52], state[61], state[62], state[64]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r19(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[26], state[25], state[23], state[22]); + const bitslice_t g1 = f_b_bs(state[20], state[16], state[14], state[13]); + const bitslice_t g2 = f_b_bs(state[11], state[7], state[5], state[2]); + const bitslice_t g3 = f_b_bs(state[0], state[48], state[50], state[52]); + const bitslice_t g4 = f_a_bs(state[53], state[62], state[63], state[65]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r20(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[25], state[24], state[22], state[21]); + const bitslice_t g1 = f_b_bs(state[19], state[15], state[13], state[12]); + const bitslice_t g2 = f_b_bs(state[10], state[6], state[4], state[1]); + const bitslice_t g3 = f_b_bs(state[48], state[49], state[51], state[53]); + const bitslice_t g4 = f_a_bs(state[54], state[63], state[64], state[66]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r21(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[24], state[23], state[21], state[20]); + const bitslice_t g1 = f_b_bs(state[18], state[14], state[12], state[11]); + const bitslice_t g2 = f_b_bs(state[9], state[5], state[3], state[0]); + const bitslice_t g3 = f_b_bs(state[49], state[50], state[52], state[54]); + const bitslice_t g4 = f_a_bs(state[55], state[64], state[65], state[67]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r22(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[23], state[22], state[20], state[19]); + const bitslice_t g1 = f_b_bs(state[17], state[13], state[11], state[10]); + const bitslice_t g2 = f_b_bs(state[8], state[4], state[2], state[48]); + const bitslice_t g3 = f_b_bs(state[50], state[51], state[53], state[55]); + const bitslice_t g4 = f_a_bs(state[56], state[65], state[66], state[68]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r23(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[22], state[21], state[19], state[18]); + const bitslice_t g1 = f_b_bs(state[16], state[12], state[10], state[9]); + const bitslice_t g2 = f_b_bs(state[7], state[3], state[1], state[49]); + const bitslice_t g3 = f_b_bs(state[51], state[52], state[54], state[56]); + const bitslice_t g4 = f_a_bs(state[57], state[66], state[67], state[69]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r24(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[21], state[20], state[18], state[17]); + const bitslice_t g1 = f_b_bs(state[15], state[11], state[9], state[8]); + const bitslice_t g2 = f_b_bs(state[6], state[2], state[0], state[50]); + const bitslice_t g3 = f_b_bs(state[52], state[53], state[55], state[57]); + const bitslice_t g4 = f_a_bs(state[58], state[67], state[68], state[70]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r25(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[20], state[19], state[17], state[16]); + const bitslice_t g1 = f_b_bs(state[14], state[10], state[8], state[7]); + const bitslice_t g2 = f_b_bs(state[5], state[1], state[48], state[51]); + const bitslice_t g3 = f_b_bs(state[53], state[54], state[56], state[58]); + const bitslice_t g4 = f_a_bs(state[59], state[68], state[69], state[71]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r26(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[19], state[18], state[16], state[15]); + const bitslice_t g1 = f_b_bs(state[13], state[9], state[7], state[6]); + const bitslice_t g2 = f_b_bs(state[4], state[0], state[49], state[52]); + const bitslice_t g3 = f_b_bs(state[54], state[55], state[57], state[59]); + const bitslice_t g4 = f_a_bs(state[60], state[69], state[70], state[72]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r27(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[18], state[17], state[15], state[14]); + const bitslice_t g1 = f_b_bs(state[12], state[8], state[6], state[5]); + const bitslice_t g2 = f_b_bs(state[3], state[48], state[50], state[53]); + const bitslice_t g3 = f_b_bs(state[55], state[56], state[58], state[60]); + const bitslice_t g4 = f_a_bs(state[61], state[70], state[71], state[73]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r28(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[17], state[16], state[14], state[13]); + const bitslice_t g1 = f_b_bs(state[11], state[7], state[5], state[4]); + const bitslice_t g2 = f_b_bs(state[2], state[49], state[51], state[54]); + const bitslice_t g3 = f_b_bs(state[56], state[57], state[59], state[61]); + const bitslice_t g4 = f_a_bs(state[62], state[71], state[72], state[74]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r29(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[16], state[15], state[13], state[12]); + const bitslice_t g1 = f_b_bs(state[10], state[6], state[4], state[3]); + const bitslice_t g2 = f_b_bs(state[1], state[50], state[52], state[55]); + const bitslice_t g3 = f_b_bs(state[57], state[58], state[60], state[62]); + const bitslice_t g4 = f_a_bs(state[63], state[72], state[73], state[75]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r30(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[15], state[14], state[12], state[11]); + const bitslice_t g1 = f_b_bs(state[9], state[5], state[3], state[2]); + const bitslice_t g2 = f_b_bs(state[0], state[51], state[53], state[56]); + const bitslice_t g3 = f_b_bs(state[58], state[59], state[61], state[63]); + const bitslice_t g4 = f_a_bs(state[64], state[73], state[74], state[76]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +static inline bitslice_t bs_filter_at_r31(const bitslice_t state[STATE_ARR_LEN]) { + const bitslice_t g0 = f_a_bs(state[14], state[13], state[11], state[10]); + const bitslice_t g1 = f_b_bs(state[8], state[4], state[2], state[1]); + const bitslice_t g2 = f_b_bs(state[48], state[52], state[54], state[57]); + const bitslice_t g3 = f_b_bs(state[59], state[60], state[62], state[64]); + const bitslice_t g4 = f_a_bs(state[65], state[74], state[75], state[77]); + return f_c_bs(g0, g1, g2, g3, g4); +} + +// ---- Per-round LFSR kernels ---- +static inline bitslice_t bs_lfsr_at_r0(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[1] ^ state[4] ^ state[5] ^ state[6] ^ state[17] ^ state[21] ^ state[24] ^ state[25] ^ state[31] ^ state[39] ^ state[40] ^ state[41] ^ state[44] ^ state[45] ^ state[47]; +} + +static inline bitslice_t bs_lfsr_at_r1(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[3] ^ state[4] ^ state[5] ^ state[16] ^ state[20] ^ state[23] ^ state[24] ^ state[30] ^ state[38] ^ state[39] ^ state[40] ^ state[43] ^ state[44] ^ state[46] ^ state[48]; +} + +static inline bitslice_t bs_lfsr_at_r2(const bitslice_t state[STATE_ARR_LEN]) { + return state[2] ^ state[3] ^ state[4] ^ state[15] ^ state[19] ^ state[22] ^ state[23] ^ state[29] ^ state[37] ^ state[38] ^ state[39] ^ state[42] ^ state[43] ^ state[45] ^ state[48] ^ state[49]; +} + +static inline bitslice_t bs_lfsr_at_r3(const bitslice_t state[STATE_ARR_LEN]) { + return state[1] ^ state[2] ^ state[3] ^ state[14] ^ state[18] ^ state[21] ^ state[22] ^ state[28] ^ state[36] ^ state[37] ^ state[38] ^ state[41] ^ state[42] ^ state[44] ^ state[49] ^ state[50]; +} + +static inline bitslice_t bs_lfsr_at_r4(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[1] ^ state[2] ^ state[13] ^ state[17] ^ state[20] ^ state[21] ^ state[27] ^ state[35] ^ state[36] ^ state[37] ^ state[40] ^ state[41] ^ state[43] ^ state[50] ^ state[51]; +} + +static inline bitslice_t bs_lfsr_at_r5(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[1] ^ state[12] ^ state[16] ^ state[19] ^ state[20] ^ state[26] ^ state[34] ^ state[35] ^ state[36] ^ state[39] ^ state[40] ^ state[42] ^ state[48] ^ state[51] ^ state[52]; +} + +static inline bitslice_t bs_lfsr_at_r6(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[11] ^ state[15] ^ state[18] ^ state[19] ^ state[25] ^ state[33] ^ state[34] ^ state[35] ^ state[38] ^ state[39] ^ state[41] ^ state[48] ^ state[49] ^ state[52] ^ state[53]; +} + +static inline bitslice_t bs_lfsr_at_r7(const bitslice_t state[STATE_ARR_LEN]) { + return state[10] ^ state[14] ^ state[17] ^ state[18] ^ state[24] ^ state[32] ^ state[33] ^ state[34] ^ state[37] ^ state[38] ^ state[40] ^ state[48] ^ state[49] ^ state[50] ^ state[53] ^ state[54]; +} + +static inline bitslice_t bs_lfsr_at_r8(const bitslice_t state[STATE_ARR_LEN]) { + return state[9] ^ state[13] ^ state[16] ^ state[17] ^ state[23] ^ state[31] ^ state[32] ^ state[33] ^ state[36] ^ state[37] ^ state[39] ^ state[49] ^ state[50] ^ state[51] ^ state[54] ^ state[55]; +} + +static inline bitslice_t bs_lfsr_at_r9(const bitslice_t state[STATE_ARR_LEN]) { + return state[8] ^ state[12] ^ state[15] ^ state[16] ^ state[22] ^ state[30] ^ state[31] ^ state[32] ^ state[35] ^ state[36] ^ state[38] ^ state[50] ^ state[51] ^ state[52] ^ state[55] ^ state[56]; +} + +static inline bitslice_t bs_lfsr_at_r10(const bitslice_t state[STATE_ARR_LEN]) { + return state[7] ^ state[11] ^ state[14] ^ state[15] ^ state[21] ^ state[29] ^ state[30] ^ state[31] ^ state[34] ^ state[35] ^ state[37] ^ state[51] ^ state[52] ^ state[53] ^ state[56] ^ state[57]; +} + +static inline bitslice_t bs_lfsr_at_r11(const bitslice_t state[STATE_ARR_LEN]) { + return state[6] ^ state[10] ^ state[13] ^ state[14] ^ state[20] ^ state[28] ^ state[29] ^ state[30] ^ state[33] ^ state[34] ^ state[36] ^ state[52] ^ state[53] ^ state[54] ^ state[57] ^ state[58]; +} + +static inline bitslice_t bs_lfsr_at_r12(const bitslice_t state[STATE_ARR_LEN]) { + return state[5] ^ state[9] ^ state[12] ^ state[13] ^ state[19] ^ state[27] ^ state[28] ^ state[29] ^ state[32] ^ state[33] ^ state[35] ^ state[53] ^ state[54] ^ state[55] ^ state[58] ^ state[59]; +} + +static inline bitslice_t bs_lfsr_at_r13(const bitslice_t state[STATE_ARR_LEN]) { + return state[4] ^ state[8] ^ state[11] ^ state[12] ^ state[18] ^ state[26] ^ state[27] ^ state[28] ^ state[31] ^ state[32] ^ state[34] ^ state[54] ^ state[55] ^ state[56] ^ state[59] ^ state[60]; +} + +static inline bitslice_t bs_lfsr_at_r14(const bitslice_t state[STATE_ARR_LEN]) { + return state[3] ^ state[7] ^ state[10] ^ state[11] ^ state[17] ^ state[25] ^ state[26] ^ state[27] ^ state[30] ^ state[31] ^ state[33] ^ state[55] ^ state[56] ^ state[57] ^ state[60] ^ state[61]; +} + +static inline bitslice_t bs_lfsr_at_r15(const bitslice_t state[STATE_ARR_LEN]) { + return state[2] ^ state[6] ^ state[9] ^ state[10] ^ state[16] ^ state[24] ^ state[25] ^ state[26] ^ state[29] ^ state[30] ^ state[32] ^ state[56] ^ state[57] ^ state[58] ^ state[61] ^ state[62]; +} + +static inline bitslice_t bs_lfsr_at_r16(const bitslice_t state[STATE_ARR_LEN]) { + return state[1] ^ state[5] ^ state[8] ^ state[9] ^ state[15] ^ state[23] ^ state[24] ^ state[25] ^ state[28] ^ state[29] ^ state[31] ^ state[57] ^ state[58] ^ state[59] ^ state[62] ^ state[63]; +} + +static inline bitslice_t bs_lfsr_at_r17(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[4] ^ state[7] ^ state[8] ^ state[14] ^ state[22] ^ state[23] ^ state[24] ^ state[27] ^ state[28] ^ state[30] ^ state[58] ^ state[59] ^ state[60] ^ state[63] ^ state[64]; +} + +static inline bitslice_t bs_lfsr_at_r18(const bitslice_t state[STATE_ARR_LEN]) { + return state[3] ^ state[6] ^ state[7] ^ state[13] ^ state[21] ^ state[22] ^ state[23] ^ state[26] ^ state[27] ^ state[29] ^ state[48] ^ state[59] ^ state[60] ^ state[61] ^ state[64] ^ state[65]; +} + +static inline bitslice_t bs_lfsr_at_r19(const bitslice_t state[STATE_ARR_LEN]) { + return state[2] ^ state[5] ^ state[6] ^ state[12] ^ state[20] ^ state[21] ^ state[22] ^ state[25] ^ state[26] ^ state[28] ^ state[49] ^ state[60] ^ state[61] ^ state[62] ^ state[65] ^ state[66]; +} + +static inline bitslice_t bs_lfsr_at_r20(const bitslice_t state[STATE_ARR_LEN]) { + return state[1] ^ state[4] ^ state[5] ^ state[11] ^ state[19] ^ state[20] ^ state[21] ^ state[24] ^ state[25] ^ state[27] ^ state[50] ^ state[61] ^ state[62] ^ state[63] ^ state[66] ^ state[67]; +} + +static inline bitslice_t bs_lfsr_at_r21(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[3] ^ state[4] ^ state[10] ^ state[18] ^ state[19] ^ state[20] ^ state[23] ^ state[24] ^ state[26] ^ state[51] ^ state[62] ^ state[63] ^ state[64] ^ state[67] ^ state[68]; +} + +static inline bitslice_t bs_lfsr_at_r22(const bitslice_t state[STATE_ARR_LEN]) { + return state[2] ^ state[3] ^ state[9] ^ state[17] ^ state[18] ^ state[19] ^ state[22] ^ state[23] ^ state[25] ^ state[48] ^ state[52] ^ state[63] ^ state[64] ^ state[65] ^ state[68] ^ state[69]; +} + +static inline bitslice_t bs_lfsr_at_r23(const bitslice_t state[STATE_ARR_LEN]) { + return state[1] ^ state[2] ^ state[8] ^ state[16] ^ state[17] ^ state[18] ^ state[21] ^ state[22] ^ state[24] ^ state[49] ^ state[53] ^ state[64] ^ state[65] ^ state[66] ^ state[69] ^ state[70]; +} + +static inline bitslice_t bs_lfsr_at_r24(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[1] ^ state[7] ^ state[15] ^ state[16] ^ state[17] ^ state[20] ^ state[21] ^ state[23] ^ state[50] ^ state[54] ^ state[65] ^ state[66] ^ state[67] ^ state[70] ^ state[71]; +} + +static inline bitslice_t bs_lfsr_at_r25(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[6] ^ state[14] ^ state[15] ^ state[16] ^ state[19] ^ state[20] ^ state[22] ^ state[48] ^ state[51] ^ state[55] ^ state[66] ^ state[67] ^ state[68] ^ state[71] ^ state[72]; +} + +static inline bitslice_t bs_lfsr_at_r26(const bitslice_t state[STATE_ARR_LEN]) { + return state[5] ^ state[13] ^ state[14] ^ state[15] ^ state[18] ^ state[19] ^ state[21] ^ state[48] ^ state[49] ^ state[52] ^ state[56] ^ state[67] ^ state[68] ^ state[69] ^ state[72] ^ state[73]; +} + +static inline bitslice_t bs_lfsr_at_r27(const bitslice_t state[STATE_ARR_LEN]) { + return state[4] ^ state[12] ^ state[13] ^ state[14] ^ state[17] ^ state[18] ^ state[20] ^ state[49] ^ state[50] ^ state[53] ^ state[57] ^ state[68] ^ state[69] ^ state[70] ^ state[73] ^ state[74]; +} + +static inline bitslice_t bs_lfsr_at_r28(const bitslice_t state[STATE_ARR_LEN]) { + return state[3] ^ state[11] ^ state[12] ^ state[13] ^ state[16] ^ state[17] ^ state[19] ^ state[50] ^ state[51] ^ state[54] ^ state[58] ^ state[69] ^ state[70] ^ state[71] ^ state[74] ^ state[75]; +} + +static inline bitslice_t bs_lfsr_at_r29(const bitslice_t state[STATE_ARR_LEN]) { + return state[2] ^ state[10] ^ state[11] ^ state[12] ^ state[15] ^ state[16] ^ state[18] ^ state[51] ^ state[52] ^ state[55] ^ state[59] ^ state[70] ^ state[71] ^ state[72] ^ state[75] ^ state[76]; +} + +static inline bitslice_t bs_lfsr_at_r30(const bitslice_t state[STATE_ARR_LEN]) { + return state[1] ^ state[9] ^ state[10] ^ state[11] ^ state[14] ^ state[15] ^ state[17] ^ state[52] ^ state[53] ^ state[56] ^ state[60] ^ state[71] ^ state[72] ^ state[73] ^ state[76] ^ state[77]; +} + +static inline bitslice_t bs_lfsr_at_r31(const bitslice_t state[STATE_ARR_LEN]) { + return state[0] ^ state[8] ^ state[9] ^ state[10] ^ state[13] ^ state[14] ^ state[16] ^ state[53] ^ state[54] ^ state[57] ^ state[61] ^ state[72] ^ state[73] ^ state[74] ^ state[77] ^ state[78]; +} + +// ---- Dispatchers (jump-table for variable r) ---- +static inline bitslice_t bs_filter_at(const bitslice_t state[STATE_ARR_LEN], uint8_t r) { + switch(r) { + case 0: return bs_filter_at_r0(state); + case 1: return bs_filter_at_r1(state); + case 2: return bs_filter_at_r2(state); + case 3: return bs_filter_at_r3(state); + case 4: return bs_filter_at_r4(state); + case 5: return bs_filter_at_r5(state); + case 6: return bs_filter_at_r6(state); + case 7: return bs_filter_at_r7(state); + case 8: return bs_filter_at_r8(state); + case 9: return bs_filter_at_r9(state); + case 10: return bs_filter_at_r10(state); + case 11: return bs_filter_at_r11(state); + case 12: return bs_filter_at_r12(state); + case 13: return bs_filter_at_r13(state); + case 14: return bs_filter_at_r14(state); + case 15: return bs_filter_at_r15(state); + case 16: return bs_filter_at_r16(state); + case 17: return bs_filter_at_r17(state); + case 18: return bs_filter_at_r18(state); + case 19: return bs_filter_at_r19(state); + case 20: return bs_filter_at_r20(state); + case 21: return bs_filter_at_r21(state); + case 22: return bs_filter_at_r22(state); + case 23: return bs_filter_at_r23(state); + case 24: return bs_filter_at_r24(state); + case 25: return bs_filter_at_r25(state); + case 26: return bs_filter_at_r26(state); + case 27: return bs_filter_at_r27(state); + case 28: return bs_filter_at_r28(state); + case 29: return bs_filter_at_r29(state); + case 30: return bs_filter_at_r30(state); + case 31: return bs_filter_at_r31(state); + default: return 0; + } +} + +static inline bitslice_t bs_lfsr_at(const bitslice_t state[STATE_ARR_LEN], uint8_t r) { + switch(r) { + case 0: return bs_lfsr_at_r0(state); + case 1: return bs_lfsr_at_r1(state); + case 2: return bs_lfsr_at_r2(state); + case 3: return bs_lfsr_at_r3(state); + case 4: return bs_lfsr_at_r4(state); + case 5: return bs_lfsr_at_r5(state); + case 6: return bs_lfsr_at_r6(state); + case 7: return bs_lfsr_at_r7(state); + case 8: return bs_lfsr_at_r8(state); + case 9: return bs_lfsr_at_r9(state); + case 10: return bs_lfsr_at_r10(state); + case 11: return bs_lfsr_at_r11(state); + case 12: return bs_lfsr_at_r12(state); + case 13: return bs_lfsr_at_r13(state); + case 14: return bs_lfsr_at_r14(state); + case 15: return bs_lfsr_at_r15(state); + case 16: return bs_lfsr_at_r16(state); + case 17: return bs_lfsr_at_r17(state); + case 18: return bs_lfsr_at_r18(state); + case 19: return bs_lfsr_at_r19(state); + case 20: return bs_lfsr_at_r20(state); + case 21: return bs_lfsr_at_r21(state); + case 22: return bs_lfsr_at_r22(state); + case 23: return bs_lfsr_at_r23(state); + case 24: return bs_lfsr_at_r24(state); + case 25: return bs_lfsr_at_r25(state); + case 26: return bs_lfsr_at_r26(state); + case 27: return bs_lfsr_at_r27(state); + case 28: return bs_lfsr_at_r28(state); + case 29: return bs_lfsr_at_r29(state); + case 30: return bs_lfsr_at_r30(state); + case 31: return bs_lfsr_at_r31(state); + default: return 0; + } +} + +#endif // HELL_KERNELS_GEN_H diff --git a/applications/main/subghz/helpers/subghz_hitag2_bf.c b/applications/main/subghz/helpers/subghz_hitag2_bf.c index 2df708a9..a8544455 100644 --- a/applications/main/subghz/helpers/subghz_hitag2_bf.c +++ b/applications/main/subghz/helpers/subghz_hitag2_bf.c @@ -27,7 +27,12 @@ // L5 (Hitag2Hell): full L0 sweep is 2^20 slots. We split into chunks so we // can call progress + yield often, and also to allow the caller to cancel // promptly. Chunk size = 4096 slots ~= a few seconds each on Cortex-M4. -#define SUBGHZ_HITAG2_BF_L5_CHUNK_SIZE 4096U +// Small L5 chunk so cancel/progress are polled between kernel calls often +// enough to keep BACK responsive (the outer loop re-checks instance->cancel per +// chunk). 256 matches qUnleashed's work-stealing chunk. A big chunk (e.g. 4096) +// could run for minutes on the M4 before the outer cancel check ran, freezing +// the UI while BACK's furi_thread_join waited. +#define SUBGHZ_HITAG2_BF_L5_CHUNK_SIZE 256U #define SUBGHZ_HITAG2_BF_L5_TOTAL_SLOTS (1UL << 20) struct SubGhzHitag2Bf { @@ -106,6 +111,23 @@ uint32_t subghz_hitag2_bf_get_uid(const SubGhzHitag2Bf* instance) { return instance->capture_count > 0 ? instance->captures[0].uid : 0; } +bool subghz_hitag2_bf_get_capture( + const SubGhzHitag2Bf* instance, + uint8_t index, + uint32_t* uid_out, + uint16_t* control_out, + uint8_t* button_out, + uint32_t* hop_out) { + furi_check(instance); + if(index >= instance->capture_count) return false; + const SubGhzHitag2BfCapture* cap = &instance->captures[index]; + if(uid_out) *uid_out = cap->uid; + if(control_out) *control_out = cap->control; + if(button_out) *button_out = cap->button; + if(hop_out) *hop_out = cap->hop; + return true; +} + void subghz_hitag2_bf_set_levels(SubGhzHitag2Bf* instance, uint8_t levels_mask) { furi_check(instance); instance->levels_mask = levels_mask; @@ -569,7 +591,9 @@ static bool subghz_hitag2_bf_hell_progress( return false; } } - furi_delay_ms(1); + // No furi_delay_ms here: it throttled throughput (a forced 1ms sleep per + // progress tick). The worker thread already yields via the RTOS; the UI + // stays alive through the event loop, and cancel is polled above. return true; } @@ -597,7 +621,10 @@ static bool subghz_hitag2_bf_try_hell_on_capture( Hitag2HellConfig cfg = {0}; cfg.progress_cb = subghz_hitag2_bf_hell_progress; cfg.progress_ctx = &bridge; - cfg.timeout_ms = 0; + // Backstop: force the kernel to return within ~500ms even if it hasn't + // hit its internal 1024-slot progress checkpoint, so the outer loop + // re-checks instance->cancel and BACK stays responsive. + cfg.timeout_ms = 500; cfg.now_ms_cb = furi_get_tick; cfg.l0_start = chunk_start; cfg.l0_end = chunk_start + SUBGHZ_HITAG2_BF_L5_CHUNK_SIZE; @@ -650,6 +677,18 @@ static bool subghz_hitag2_bf_run_l5( // attack on additional captures would multiply the wall time without much // gain (candidates from cap[0] already include the true key). if(instance->capture_count == 0) return false; + // Emit an initial L5 frame so the UI switches from "L4 100%" to + // "Hitag2Hell 0%" immediately, instead of appearing frozen on L4 during the + // first (minutes-long on M4) computation window before the kernel's own + // progress callback fires. + if(progress_cb) { + progress_cb( + SubGhzHitag2BfLevelHitag2Hell, + "Hitag2Hell", + 0, + instance->keys_tested_total, + context); + } return subghz_hitag2_bf_try_hell_on_capture( instance, &instance->captures[0], progress_cb, context); } diff --git a/applications/main/subghz/helpers/subghz_hitag2_bf.h b/applications/main/subghz/helpers/subghz_hitag2_bf.h index 1dd3b6be..281e5017 100644 --- a/applications/main/subghz/helpers/subghz_hitag2_bf.h +++ b/applications/main/subghz/helpers/subghz_hitag2_bf.h @@ -77,6 +77,23 @@ bool subghz_hitag2_bf_add_capture( uint8_t subghz_hitag2_bf_get_capture_count(const SubGhzHitag2Bf* instance); uint32_t subghz_hitag2_bf_get_uid(const SubGhzHitag2Bf* instance); +/** + * Retrieve the fields of a single stored capture by index. Used to build the + * BLE compute-offload request. Returns false if index is out of range. + * @param index capture index (0 .. capture_count-1) + * @param uid_out optional output for the 32-bit UID + * @param control_out optional output for the 10-bit rolling counter + * @param button_out optional output for the 4-bit button + * @param hop_out optional output for the 32-bit observed auth + */ +bool subghz_hitag2_bf_get_capture( + const SubGhzHitag2Bf* instance, + uint8_t index, + uint32_t* uid_out, + uint16_t* control_out, + uint8_t* button_out, + uint32_t* hop_out); + /** * Set which levels are enabled. Bitmask: (1< +// The firmware is built at -Og by default (debug-friendly). This bitslice +// kernel is the single hottest integer loop in the app; force -O3 + loop +// unrolling on it alone so the M4 runs it at full speed (empirically ~2-4x vs +// -Og) without changing the rest of the firmware's build. Pure integer +// AND/OR/XOR/shift work, no FPU, so this is safe and deterministic. +#pragma GCC optimize("O3", "unroll-loops") + // --------------------------------------------------------------------------- // Bitslice type (32-way SIMD across 32 lanes of a uint32_t) // --------------------------------------------------------------------------- @@ -54,6 +61,14 @@ typedef uint32_t bitslice_t; // index is state[77] (in round 31 filter with p=1 -> 47+31-1 = 77). #define STATE_ARR_LEN 80U +// Per-round specialized filter/LFSR kernels with compile-time-constant state[] +// indices (bit-identical to the runtime-index bs_filter_at/bs_lfsr_at below). +// Ported from qUnleashed; requires bitslice_t, STATE_ARR_LEN and the f_*_bs +// macros above, which are byte-identical between the two trees. Using these at +// the hot call sites replaces per-call index arithmetic + non-inlined dispatch +// with immediate-offset loads, a further speedup on top of -O3. +#include "hell_kernels_gen.h" + // --------------------------------------------------------------------------- // Layer 0 mask & layer new-bit tables (see gen_kernel.py) // --------------------------------------------------------------------------- @@ -172,49 +187,11 @@ static void emit_candidate(Hitag2HellResult* result, uint64_t state31) { // after layer 8). // --------------------------------------------------------------------------- -// Compute filter output at round r, given fully-populated state[0..N] bitslices. -static bitslice_t bs_filter_at(const bitslice_t state[STATE_ARR_LEN], uint8_t r) { - // Group bit-positions per round r (position p in S_r maps to state[p-r] or - // state[47+r-p]). This mirrors the scalar hitag2_fiat_filter. - static const int8_t group_pos[5][4] = { - {41, 42, 44, 45}, - {32, 33, 35, 39}, - {21, 24, 26, 30}, - {14, 16, 18, 19}, - { 1, 3, 4, 13}, - }; - static const bool is_fa[5] = {true, false, false, false, true}; - - bitslice_t g[5]; - for(uint8_t gi = 0; gi < 5U; gi++) { - uint8_t idx[4]; - for(uint8_t k = 0; k < 4U; k++) { - int8_t p = group_pos[gi][k]; - idx[k] = (uint8_t)((p >= r) ? (p - r) : (47 + r - p)); - } - // truth(fa or fb, fi(bit[idx0],...,bit[idx3])) == f_x_bs(bit[idx3],...,bit[idx0]) - bitslice_t a = state[idx[3]]; - bitslice_t b = state[idx[2]]; - bitslice_t c = state[idx[1]]; - bitslice_t d = state[idx[0]]; - g[gi] = is_fa[gi] ? f_a_bs(a, b, c, d) : f_b_bs(a, b, c, d); - } - return f_c_bs(g[0], g[1], g[2], g[3], g[4]); -} - -// Compute state[48+r] = LFSR feedback at round r (deterministic once all the -// input state[] bits are known). -static bitslice_t bs_lfsr_at(const bitslice_t state[STATE_ARR_LEN], uint8_t r) { - // LFSR taps in state at round r: positions {0,1,4,5,6,17,21,24,25,31,39,40,41,44,45,47}. - static const uint8_t taps[16] = {0, 1, 4, 5, 6, 17, 21, 24, 25, 31, 39, 40, 41, 44, 45, 47}; - bitslice_t v = 0; - for(uint8_t i = 0; i < 16U; i++) { - uint8_t p = taps[i]; - uint8_t idx = (uint8_t)((p >= r) ? (p - r) : (47 + r - p)); - v ^= state[idx]; - } - return v; -} +// The former runtime-index bs_filter_at(state, r) / bs_lfsr_at(state, r) were +// replaced by the compile-time-constant per-round kernels in hell_kernels_gen.h +// (bs_filter_at_rN / bs_lfsr_at_rN), which are bit-identical but far faster on +// the M4 (immediate-offset loads, fully inlined). All call sites now use the +// specialized kernels, so the runtime versions were removed. // --------------------------------------------------------------------------- // Deep search: enters with state[] populated with L0+L1(spread+scalar) bits. @@ -232,7 +209,7 @@ static bool deep_search( for(uint8_t k = 0; k < 5U; k++) { state[k_layer2_bits[k]] = bs_from_bit((i2 >> k) & 1U); } - bitslice_t f2 = bs_filter_at(state, 2); + bitslice_t f2 = bs_filter_at_r2(state); bitslice_t alive2 = alive_mask & ~(f2 ^ ctx->keystream[2]); if(alive2 == 0) continue; @@ -241,7 +218,7 @@ static bool deep_search( for(uint8_t k = 0; k < 4U; k++) { state[k_layer3_bits[k]] = bs_from_bit((i3 >> k) & 1U); } - bitslice_t f3 = bs_filter_at(state, 3); + bitslice_t f3 = bs_filter_at_r3(state); bitslice_t alive3 = alive2 & ~(f3 ^ ctx->keystream[3]); if(alive3 == 0) continue; @@ -250,7 +227,7 @@ static bool deep_search( for(uint8_t k = 0; k < 2U; k++) { state[k_layer4_bits[k]] = bs_from_bit((i4 >> k) & 1U); } - bitslice_t f4 = bs_filter_at(state, 4); + bitslice_t f4 = bs_filter_at_r4(state); bitslice_t alive4 = alive3 & ~(f4 ^ ctx->keystream[4]); if(alive4 == 0) continue; @@ -258,7 +235,7 @@ static bool deep_search( for(uint8_t k = 0; k < 2U; k++) { state[k_layer5_bits[k]] = bs_from_bit((i5 >> k) & 1U); } - bitslice_t f5 = bs_filter_at(state, 5); + bitslice_t f5 = bs_filter_at_r5(state); bitslice_t alive5 = alive4 & ~(f5 ^ ctx->keystream[5]); if(alive5 == 0) continue; @@ -266,7 +243,7 @@ static bool deep_search( for(uint8_t k = 0; k < 2U; k++) { state[k_layer6_bits[k]] = bs_from_bit((i6 >> k) & 1U); } - bitslice_t f6 = bs_filter_at(state, 6); + bitslice_t f6 = bs_filter_at_r6(state); bitslice_t alive6 = alive5 & ~(f6 ^ ctx->keystream[6]); if(alive6 == 0) continue; @@ -274,7 +251,7 @@ static bool deep_search( for(uint8_t k = 0; k < 2U; k++) { state[k_layer7_bits[k]] = bs_from_bit((i7 >> k) & 1U); } - bitslice_t f7 = bs_filter_at(state, 7); + bitslice_t f7 = bs_filter_at_r7(state); bitslice_t alive7 = alive6 & ~(f7 ^ ctx->keystream[7]); if(alive7 == 0) continue; @@ -282,7 +259,7 @@ static bool deep_search( for(uint8_t k = 0; k < 2U; k++) { state[k_layer8_bits[k]] = bs_from_bit((i8 >> k) & 1U); } - bitslice_t f8 = bs_filter_at(state, 8); + bitslice_t f8 = bs_filter_at_r8(state); bitslice_t alive8 = alive7 & ~(f8 ^ ctx->keystream[8]); if(alive8 == 0) continue; @@ -320,22 +297,75 @@ static bool deep_search( // Now state[0..54] all set. // Verify LFSR consistency for rounds 2..6 (state[50..54]). // Any lane inconsistent means the guesses were wrong. - for(uint8_t r = 2; r < 7U; r++) { - bitslice_t expected = bs_lfsr_at(state, r); - alive8 &= ~(expected ^ state[48 + r]); - if(alive8 == 0) break; - } + // Unrolled with compile-time-constant kernels. +#define HELL_LFSR_VERIFY(R) \ + alive8 &= ~(bs_lfsr_at_r##R(state) ^ state[48 + (R)]); \ + if(alive8 == 0) goto lfsr_verify_done; + HELL_LFSR_VERIFY(2) + HELL_LFSR_VERIFY(3) + HELL_LFSR_VERIFY(4) + HELL_LFSR_VERIFY(5) + HELL_LFSR_VERIFY(6) +#undef HELL_LFSR_VERIFY + lfsr_verify_done: if(alive8 == 0) continue; // Compute state[55..79] via LFSR (deterministic). - for(uint8_t r = 7; r < 32U; r++) { - state[48 + r] = bs_lfsr_at(state, r); - } +#define HELL_LFSR_COMPUTE(R) state[48 + (R)] = bs_lfsr_at_r##R(state); + HELL_LFSR_COMPUTE(7) + HELL_LFSR_COMPUTE(8) + HELL_LFSR_COMPUTE(9) + HELL_LFSR_COMPUTE(10) + HELL_LFSR_COMPUTE(11) + HELL_LFSR_COMPUTE(12) + HELL_LFSR_COMPUTE(13) + HELL_LFSR_COMPUTE(14) + HELL_LFSR_COMPUTE(15) + HELL_LFSR_COMPUTE(16) + HELL_LFSR_COMPUTE(17) + HELL_LFSR_COMPUTE(18) + HELL_LFSR_COMPUTE(19) + HELL_LFSR_COMPUTE(20) + HELL_LFSR_COMPUTE(21) + HELL_LFSR_COMPUTE(22) + HELL_LFSR_COMPUTE(23) + HELL_LFSR_COMPUTE(24) + HELL_LFSR_COMPUTE(25) + HELL_LFSR_COMPUTE(26) + HELL_LFSR_COMPUTE(27) + HELL_LFSR_COMPUTE(28) + HELL_LFSR_COMPUTE(29) + HELL_LFSR_COMPUTE(30) + HELL_LFSR_COMPUTE(31) +#undef HELL_LFSR_COMPUTE // Filter rounds 9..31 check. - for(uint8_t r = 9; r < 32U; r++) { - bitslice_t fr = bs_filter_at(state, r); - alive8 &= ~(fr ^ ctx->keystream[r]); - if(alive8 == 0) break; - } +#define HELL_FILTER_CHECK(R) \ + alive8 &= ~(bs_filter_at_r##R(state) ^ ctx->keystream[R]); \ + if(alive8 == 0) goto filter_check_done; + HELL_FILTER_CHECK(9) + HELL_FILTER_CHECK(10) + HELL_FILTER_CHECK(11) + HELL_FILTER_CHECK(12) + HELL_FILTER_CHECK(13) + HELL_FILTER_CHECK(14) + HELL_FILTER_CHECK(15) + HELL_FILTER_CHECK(16) + HELL_FILTER_CHECK(17) + HELL_FILTER_CHECK(18) + HELL_FILTER_CHECK(19) + HELL_FILTER_CHECK(20) + HELL_FILTER_CHECK(21) + HELL_FILTER_CHECK(22) + HELL_FILTER_CHECK(23) + HELL_FILTER_CHECK(24) + HELL_FILTER_CHECK(25) + HELL_FILTER_CHECK(26) + HELL_FILTER_CHECK(27) + HELL_FILTER_CHECK(28) + HELL_FILTER_CHECK(29) + HELL_FILTER_CHECK(30) + HELL_FILTER_CHECK(31) +#undef HELL_FILTER_CHECK + filter_check_done: if(alive8 == 0) continue; // Any surviving lanes are candidates. @@ -388,7 +418,10 @@ bool hitag2_hell_recover( } } const uint32_t l0_total = l0_end - l0_start; - const uint32_t progress_step = 1024U; + // Poll progress/cancel/timeout every 128 L0 slots. Small enough that a 256- + // slot L5 chunk gets at least one in-kernel checkpoint (so BACK is honored + // and progress advances mid-chunk), without adding meaningful overhead. + const uint32_t progress_step = 128U; uint32_t next_progress = l0_start + progress_step; uint32_t t_start = 0; if(config && config->timeout_ms > 0 && config->now_ms_cb) { @@ -431,7 +464,7 @@ bool hitag2_hell_recover( } // Round-1 filter check. - bitslice_t f1 = bs_filter_at(state, 1); + bitslice_t f1 = bs_filter_at_r1(state); bitslice_t alive = ~(f1 ^ keystream[1]); if(alive == 0) continue; diff --git a/applications/main/subghz/helpers/subghz_hitag2_hell.o b/applications/main/subghz/helpers/subghz_hitag2_hell.o new file mode 100644 index 0000000000000000000000000000000000000000..60c9b2d6f4818eca7ff2f07257a089aae2f882cb GIT binary patch literal 20136 zcmc(`dwdnux&Ob`%uaSPxsnTkgf9dFgbWZ6P_UrPP9oq$1BQ6%*PaGMZV3h@__a2k z0~Hh%6=P5=TA>9&LanywkyZOWqEN8ju%cp*ZGj}@eu0FTM3nsAYiIA)Zrj&+y}tka z_Ukp7nP=AR`8=QJSRAL|Rcvj#?nX?8woM zx=nc9xP#YZg&7{pJI|Lj&WNzhS&n*zdeJi+b(t{S@C%Nr5ec^V+EG(vl)GBQ*$pDn z4x>$vuxwYxxXVPe{c~YikBJyN(ir4sh>7kzkzjo&Vy#vg?Y4+0dyI&2E$XLB(>@|6 zxG}_-)RATdjtA{>;Iqn_M4G^ z{H8+e`^FTNJxk1eJ*H{*m3sDlWg6983+2A(VY%1cIzMZ-XmtJvJCi(-g7 z%be<7yWdf7%Rz4b%Z_?L4z|~uY4+1{kbN=N>ATuYbax5E`lAtRFO`w@Z8E`LhW(G2 zL)=$Il68xSbbXVA`l&I)U2v;VpNZk_{BofZ8Bf7Bp^h7w_VtH_+8~D7AHv(ajIr*W zQRpR2YnBmf?S(hbn_l;GGo|DwQWU)wE(X1Mzqo60Pk+%j70$k{g+4IL+1IlqcVAy= z-oA_F*X#r1O3klCaO|FGsGiw|Lg}P(>aNQXccqueqIq6N`6fBa&2`ju73jRfQCiRM z=R4|dFwQM@)IDHabhD#Q$1TjyneM2&u)%_R95vHSvgdDbRGCP28w_F3+3TphXB{;W zTi$2JS{LxMG$Y!+X11fg#P{+|M~ws9k;1S?8F6l$5oiCGOh(^GyA2#4<-F1~EQL*G z;&-1JQC1V!K4K1c4;xADEU@y*F#Cttbt_mrZDzV3%LMDEGTgm+yiji$!|fYa3-yp; zx;In_HOidgI>&@sZ6>*=rEO))+~4&O%2$Yed=U@T*ovz!Sr^ zM1);wnwAefUTIkFweazV!{BQ~xpTLG4>6&j+)-zZF#E=Jj_NU_U11Kg&KNP)FTh}x z8SP#I(~qUs&fVpxOfkw$N0z^hvO?G&$#}Z~T;h!3)(RPJ-)JPmwQzeD@$6mVP7L_e z$z-=EQ>caS3H1PV7s!e3^J19WX~bJ^nOE9f#EoBwczd2P+MNV0KNUmWuVtc}EQeZe z7{lCq%poO<;n`eKF86|-Nr0;qV%{6i3@Y99CaVunkGDMiFIa-#!{r}s5K*7nhM)i&2yb(ryXpP5>n0A{tvr0O~$l?|>7 z!E9Esqt1B?^T*6})Q{mw=6pvL$YJ(8#xP^KqwEdvjaYFtHb{~lYg8sYGvnNhN${94 zO*rnTL-^TD>~jb4=!(sbiZi3^O@?qEH`3j&m`^UqFzYOs^nhoCY1wxX*I$AQIq+>H zShg6ZJzNgA7n$MqQS9{t=9!JgaQ7=A-AEB>9T#ct)HI>CnbMtnQm7luS$2!ea-YB- zUmT`w9Ys8hvi^);ZzA^l#Ax?%Gsd#WUNO zJ>XvN&COpVoT^AM+>RrbjK0-T=fP)`9Bj>4?Wl}=;*epumt&`V@|mf*_@^-4kxLv^ zLe4X49kIbkv>UOu7Dnkv-^ z>2=$&(H+!{ApWPP2vxUBD32NLcF&aRHJM~hGCZ!v_eaKPyK{t83G#BcX|`0?%IU7h zOt5U?Mm@RUA4WRrdKqq2ip$({rcX^bqOF(l`)0Pp7G`Ok zFHdyTCg$j|bBXWFdpE<2w($}y!rgJNJ8If{H)b^T_IFQDSbRE$Wa(;2YoQ6Zgk9ikG6LR zb&Q;+p19SH{t?)xq1;jA%x?V~*w!mm197B%Kkap(2fBCA=N4k=QOZPsgG;*|jIZA7 zqdnpS&+3s)*L9+#s%K2?BE@Ym{yR z(trI$p~9(ujJRBg&6>&U%ILqbR;YRSZGEm(aWdO&m=EsKQzC}SqK5t7&)RB4yLH>Z ziCm|-%27{>!EVDssq(;Kt{mhx<4+smYW>TOT24D1=&$dq(Q%`|2fo1m*dY-PZ8ZkF z^3Z5={e**qp;J#5LqF0jJ$+)-T)2-xITWG_juR6x|2zk#&5mC~L-8GjV zBj#LBnVpHUsAIGC>lEq=)ybF|Swe+k8$YOP{Q-h6sd;b&k@pglgt>`qw$HCMQ!gn>Nj$ldowocsgP<> z9I|bWVg>150FTSSxfB0c!CbvtxA!dl(MJ#dU5^hCr(MlaohOXmpvRivwrvyYAmt}! z!a*?TyjiLz!A(Ew*bJBPskT#s+$`L!>k_Jod@vad;^EwjV65@)>T=L$kXwfy7r?h0 zU2kXJyjZJqI>v$CPnv0z73yJdIe)8E=Z$#x+?)8xDM#(Ympf1De&gJZ6WVsk?l~~+ z*4R{$C#4ZDM4a2bfjMrIkTu}?Q9Wh3gx=qARobR+H-xXm;00ORIji;ZYy`x#agK@Hh?GobpV_?PJlmH-)%;^=U;bJxyA>~x=W>M zfeV*&-@P9!OMU7hWA5JUQ>FMw1+l%2cFNIhBR+a@KkH`lz;3WPLN3$NFZ}f{Ecbz3 zlv_&f)Aoi`F^u(G3T@F|Dfa2YHl4=>d3>7N+3%=$aOg_&kuM~;7nVq{19$N6+D5q+ zN3CTo(!QBImGgyTNd*t)UB zZpL^AbH=aWMfYBxYFgq`Phi_t{9=ksb9>R_VPcYwGuJbgyUBSk)3yiKXNX;$@J0K< zG{)Nse+Pki3vDa}uQuY6=0aOBwtZPD&7b{X&;%BbVdD?a{6J;(0fDycP5-)CA2XFifJ|9{PKS_;G|W zUC5N`0?)b+==s;0`n_v<@n1c&xr}+)yeuKoX zunU-Xm>#+g!?MzZuD8L{eiLRIj$X@AA+;m|E7We=V0FX`t@0{#L>?+=8UO} zI$6}+iVhu7a9`VC2Xe-w=Z@dN({o^XBl>h==MUk9o+DZqug{H4-a31_h7{@tc?wyRO6fU3pf*7z)s#h7;1?K3>(A%ZoBy_kApVi}O zp0DwVal44gpTfT`=H(}t*L1#AulE#mocn?pc(aeT9Q6TX)A77{oljK}Z|Y9Li@7=m zSj1SLI>I{c!a6}7>R}ET`^*^?aAix+TJGdQZo?2zV9cwqUbv#<^Ozi{6va=X?iiTFPoXvhdB#_)Y|U zH|?jbo25UdjmHj6{oHSn>V0yLLdrcxohGpF*n)m1GzSLLhTu$`!v+z=*YoK9xH;Hu zg*%%m--<3bX#5yQCu4tc;B@du9ZvG{jyMZQ~sB5rMn#Nt(K}O-$&bU ziuU8kOcVm;*D)J*E%4$acNa6uH*ykwUfu2S9Q0w@Ftc}cJo#a)8 zGTM#?D;+~DBhKDpCfgm@`a_vywVZ-8_(n8qu5RLMcmp0;*sP8*HnK0%(S`oRgFD%KUn1h{Ji}wJCLfJA!mLLr zS1Tv`?QtC4&e6sZIochk`^G+-IVSfAFqvStW1kh$w68-S(=e@#BHGSo?NBCcw-!8g z->vv7`KYC1#fQ?e&fUX&1NP0twHC0=VD8K~;HjJm@5h71IP#gv%prEaP%})aCV8cr zKStqW(Zg$pPLqyg&>`;%dO`IuJ8@$e%jlb;jsj=vq zrEQlX)#T~SO%*0cG`bb8Xoa6cW--@EvUAJVl9uulLjU0*bG~tW2>nO_} zSKBT}MS=Zle4&;YwS@h>CVb#wnP%(x;~et|dwBRpj9p(LRXhHqV@O?xRCB;u&&}jp zb_@N3jn&4y`4YB@h9CQ+$JTrWU#oq!#xT~dZDFpVel5JIeO;(hk!qiNgZM0B>{|BA z+TLWnc8Yza<$5eJ{#>?{J?2*ITZ_Cwcb_QdA2Y+_4sh4p+E@Q1n$LV}H#?6;r>kl%^?_*obpSARdVPR zQPf6!smHJCPU!fk^W$(^`}8AXnBB@gOLMQqD5jlX}=8rMbZojB~Et-4mby%uuxZQ>ijmM^S zZ%S+yL)${V1D5r0!^L)WZ~9a#xJ_W}^@oX@x;;48hF_J+aH}2etpE?5>*d4$N5IDe z=l6ieuV}}W zR6YLFb|5Y517y(}XYi{_G_W`8&~-XbBJSWn;f$dk%yfR#9%ZO@INHiskJ4Tp{M|@j z%wyJr=v9aP9s-m0!$Lg@&-6OKwJJ8N9z2_mvo@rycCL?zRJSG0P~*WiOHOp#!09@0 zEdi64zzFWU(csnchR$=x+U;GA^3ZNG_#Pw1>=9A+3-q^NSeCY>_TMAe>p?Nz)wnUg zSl3*pb?{m*f}6%{vCvWUu%X>AF%IRk9{A2 zL+l~&9)i6J=A~o!!XDacJ^)WO=ZIY|Fn>0{r3@Ki*KK2sFTHl2OtqU22%ed4bGf5T z^lkb=V#4`3J?7j5)An$#`(53MrZB>9TK7x+;tR$a|uv*~9Q820-Vc=h3ZXGsh zAx3SW{&I5J&DgmKJnF#icKlN3qf3O>Y9Iy@)9jWi@&eP~+UvGdu`grh_-(!%T>V&5 zzClDVr?D;v%N95s!M?S|Fo(8VXovW1w_F@p7i5501iaUn)lz3H809d&C^^b~fpQHZ z%8y~A$ns+t34ZtUKDCs6giQF_EUc3CkBFkPTj0lP-mBt+4W+~}xT5i!gHLGAkOx^c zQ+3=jZ9T6wl)@VkX4imk19Rm@`aHWuXpG!zz@i0y)f~`c4Rg<8o2K!GY681P^wa&e zz~NfvrCi$J9k-?VNc^#zz%omQ*^N`BI*ZN5NUxuN@4&NK`p=Y-y37euqMK>uiZSR5 z$H2A;-)`W#2^`MCu_fTB{jm{U^QdpgvGzFH9s@VP3||#Wn4Mk^=XTH!*whfe^t_M1+M9THrq>yb_xRNPjL!s% zhN;A;lVAxS@~B_)h31ggYT6;FFVPWugSqBa6F4+q0DEy(aOz3y$&y1{|_^>fX|4cd<3 zaM@#@MaQP;@JK{j4ciuG)lVYE!>1+m)3^mYqGvVZJlmsVuSE_JVb|;jJ9ybxp>gus z&Dh)@3)g9X4zqNgaCSc9LdSVvU58EKlg0NFdN0pz#wOQLM(-884Hm8(WAD;jhc}~X z|15LFi=6#D2QMA#BP8!yXm6t!=Qe`XJ}}WSM8AL1b8(*L#WlPel4D)UTaVD*9O-r6 z6Hz7UWui#0y_?|AyX2>u7uq*+CH}`8UXMR|c~A2P+G^Ycj=Yc1YlSkc2N)d2F75E~ zUF=;)zNlm59EtDai+cWP{=!lEU4Bgm@AZsW`vKMkMd;Ze466>#)S%bHytlgmmm0v^ z6-oB}!m#H`X>sk=3ES0JT_CqKu!c8}E@iF1w9i z_|#l6*zRBra{>FcOLBJF)oU;9PaV7913JM!cdp2`Ju=Cz0jEZ|vWNM*1N{&4d?Wbj z_mEjK+S+CLwo}rA7CwC_a6J( zCNUR3d6@bg<9%wB9AfD;XfA%xe3Ex4ny2sS{n0S14%?kAXMKh3^*+7EuS15}4Gp}L z2B#9nT}SR&gFn^6CG@cB@CiMSY#^4`ZPEVawd<=i@9}@erRR@Y`e`B`$U@(hz|Na&`}}bfaUHLUTci>Fo%!A|1#ZL@Y#!hqKlOSHaZX3 zFQVM#3-nwy#tkEvJR+jN$m?E9z40=(MBD@?`gQIG2eF%>`EZPP_2dXTzu(BapL)2^ z`>c)^jN=QT8WQmrF@!aQZdcg7*nggmL+Ih+XJ`9~wIU4L5JTf~z%NI)D9QY3-EvRt})2( zu>Hfl3v9$*Bbk3%@DDxbWMI>V{XUhx%-E8hbI>;pJEE)3#hO?9m=8VnnMpaS7n{~x)H$alwhXb(9MJE(4XYX3 z^zPN1=KBd@*=KIXhw%~3VXz^_V@vY2YvHl>r?+{J-8&)&eps&lz6S2N?Wgcz#=Z%B zYr$a@=Oc`}*BoZwhrYFkb)M`YJ~I#S9n8b*>$`HaTO(%KtV{ToZWw#5PG5wSyeqdX zDZSPnFnPcnrGKd?#^)c=96swqx%X#8|f; zEEfy>8U5ddgPp9K!i~Xp1G&~*xN7h|#s`M%AL;sT?FjaNUdG3y7yr%1KVscn_B4)h z|ESC$?uFY0tRK#>$D!9O524c@A?bItd%qx-FV=C!tG^p_SBl7z^ckY4nVhH*Th-6@ zsWS$73ii=!s4)D$W-2}+SgY^HMhAG$spqkiIv+@Je*#~7SRZ3Ut7R9locXDl{7C1U z%fNgEzS|6j`W@nOV(D3II|tos-e6yY^^i2kYtVBHoLGX6rIa`D*$v>jjPqF?D_Zot zF`hL+1i9}J|NDub8$+yoInq5WqHJ<(s~$b`#AtLkEy`KPZr6E**S!U-EigZ z$yyG-Yj4$l=5b$O{ux-Kd5IHIc3Y{Az0z&Kt}Wy{E$9r_>`W13HJLeZD8}yCtT{vu z307y|d>gT49JuHl{z2;2hz!54&J~e%p)rwoG{%mR3GhC_%3Q%WVCXtZ#^{{ay@r@K z4_)_w&j-RP*_j1zJJ{Q1PhHOm57Kv29J*_LUuREYxt^yZ?K9Xb%t)}7vu@WmY6Twy zT+UJMD`HNrDeN9Nx*hH`PqoevN58@bop9kCe$oOT z8u(5~^L&pWmQU4t8tgTK2m3nY)TZlY5AQbr-i+mrVb63G^G^#LxmH-@sRr-RqnMYl zFY)py{HR6ex4>TWU_8%n$Ih#yhkd@75-|#$@A0WzVujvE)8B<%!alE&YmG(!&IX@) zOANPayg7Won_x9B!6(tBleJLm>-t@{m+v(r-7}eBqWQhqr+$HMxo~v76!snXdIbGB z!m^ebUe^#Y#39x#jIC>Wj;cjZoh#`)E{|*do}k2tv|nbgTfe9NP=;HJ(JxsVd=r?B z?2mVOtu5#01zxq-Z+J~2mKw_AhzQauegL zS?5!yiL=kMzdecgxR%`E5s_$>nuGaPIDzk(lH4+Uo&oYyM(k`nFlY4aqd<*$ciKi(`yf|FMVfNsoH? z=50OS2quW4j?qU`Tct=?-S533d*snnNg2U0$|EW#S1dj_E<#l9N`GtnDH&ffM3~hx zD{ebDC~^Jv(b1xETE&Wkr({~m&h)ppUu}r$S@ELsL}KOk?^WE6JgMZR^mn%3ZNyaD zNuu&U6RNgPs<;n%YRT*AN49@#q*PBHA}Vtdp5Fdq`k!e(u4G0<$w7R(`m$l7vLgQe z?N?XaN%U{d_E+Yh9FMET(*&l!o;PbF^M{@e6FavxLjpGIo+ z$aGP8DB-E?vk!^$XZre!mdHGnYl&&A?)&!>XUwST$481kmRz5)`Q$gbCpyDL<$TNO zTofjX`oqNeMIO$P;`~mNbBs8D+Tc7$oKM&1WS;XaSN~9+3sH2Nboz>gXxZr%MTtqy1!_hFz!0Um{-9WVuo7u{fyp{YZqME+~&RF z!wdZ>J$rfo#P@BTdxg^9xGkdY;|bQP&CLDSWRz=2O9M4}HJ8RCK9__uXAS_1D0+ zo4(&%D*7^w94aM81;2kC`6Bf*k!9oz)aCo>kxySL>Ykaychx!SImX^j|5xG{f4VmQ z%4fO%rb6wjGmUEfjl2G)y>Y7erhh}kA59^iSOV^6Owm`9!a;nWCo+ehhLel6JWlp$ zzcDsw-w|6mp5T8co(SUz=kRhwa71$OJ@*qvn5RIBLsw;T?B+PiahfAc9=a-nV*$qv zc|R{J%-dVGFmHERQQn@i8}oLS-IVu2+0A*o%5KSfrR>Vo!h#>C78SgadQ-vIskand zRVU_Oot9eAV#0^CVFhoExT>IrBlDT`f;^5rBbFEJ9C3TW?{`N``e=8=>Oq_nI3L&@ zKWW$Q_|-{!;wL5ViC=vM=johp;rxBhKmP5`vWkN{%bw%>e>wij(a-T!hw;+LH+GhN z-DAFFaK6;lrWmdMC-gzNxbBtLd`u;!N51^=#Sa4I4RW z5ZGPh{oO1>RPw(DAAgUdd!5|Zn!%bVQ~ck~sqArOh`x^Lky(*EOXB!HnSV%qT8JyA zue@)@Eg2`bZ#9NgUl}GU|0DBHsm~+dPkSp$+aZScs z$iu3~Mv2PhnMYGMA%6$?wHfaqkF4gy%gVCMcT*ono=^J=Gu}guQNnMZpe){!gyp3E|kI4{~%QAnT`XKVX$meAoLmp9`l}VhKB+f7H?Ps1ys@7cW z%XHLiGrd&DnKMyN-x%Wh*(lLhALsN{;6H5{j{58gS@~t0AYP=E&ik^zsDX0b89wac z!yZ2D;lmz2?BT;6KJ4M^>qv3>G@rAGgN?-4hA>0NySq+%cchZ*^kpC)Da;lV8;l%H z+}sd>PxFXLHUX6wu*sHLO?{X62*B90>Pn}+3RvP2h ze~fPxYF)JNw3(vXk)kqjfvD`@I>F;y86E9hDaMJ)lvj#pv?n_=o*Db`_Nbl3Go&dJ zhaW1Q@xa*kw{IN#;r5{)70;**bFRd`iNClv`(X2bI8{Htcgn#RxnF*tQ}y83!`tId z7ti8j%!5ykeQ*1V_f9zY)Yy~TuQNnqHu7KIJO1F)WBJO2M^Q#{bP@B-$h^7(B@CZ{&D*Y_f9{U$@SiQM<3L2 zThCGF$eF^!9w_W1?+OolVNEBw#h|O>r|5UP>_zVPBi0v9 zH#_R&)uM3t7hXSh9+_;P&6SnCR9T8K`^uJly z!~aTL;9Hop)5X4v(}lXoJA$uAh`z7MJ)3XlTZkomZ_M`zH5KI9o21ftgFm(!5qG6W zi=w0aPsy>t*Hrys_}r?-jGOX5;M?xwsdK8{CEsf$N3Iibwn^T4NDQ?<;{Q@UPp+{K*=JTH4M~>AnUb4SP-alKiKc8fG@J@FU zYn#UTlC^A>dzw7Yl5ut+YvDnxr_R;#zMAJhG^V>zLE@G5fRDeEu&7xkp6j?+~%2Mx-b@PybgFCod7# zYO&#M^Oz%RyjKk2Tk*EQ7@H`wS4LDe;L`(Rj4a)CslVtXWkP)_4nO*z;_RMF{VCcf zb=j#W`d@7|h3bsr-Dx7Rl^C&zv9_QyzGt_Z%>7ZkvrXiiL+XY0_doTuIi!y6Q~Un^ zRPW!Z*8?7xQ{Eq|=EPt539>$9GXk<1x$##XN9HB|e9HNMYE#FT9Bk45O@9jcZ=sG) z-&Fk8|Nm~m|8LX(_on~nP5+Nuf9{|^_M1i@^_~#q0E0Qd#k@4*(9a5|trwM7II^@#YuR?tKEpYhvq8=g!+8qlSkB>jXUgI@d-H0_5;%MEYRiZLPsHb)En8>Y z+<61#P7`N-#JPqX?Zl&<1!v6U>Ik0rweL%HgZd6fsyxB*$wx_N$ZtC1-#!abBc#i9$&SfHY%CYzjTrccw6fsjG2W?7i8qYT=VPa78B%%KO zoBnO5r{<~CF=E;|a88FugTlohuV%e)TF<3v8#CeBYoc}L6zlhxXREjvSbL~@U18ayD9aCgt%c2NT?8ieQE`}G*=^!4lKte*sHm~W5et&9Cd+Ap@I>b1?_ z7D=2<#}Bn%eMT&;&CKb0n=|jfS`6^k|5t+#`g4lV|D*h-;;28r5Y#_M9MnE>roX?x zhNG6Fj^ivxJx2paBS#ZQGe-+YD@PkgJ4XixZSz~#| z|Fn+!cY5H|e=2bPOW^#cz&V~VrE@qO>pAqe3=Vu;uvYo5Af$pcJu$KI35m%=QirFH z%*f2LCrs4Ug1^_FT>q>0r++efT+g4DT|aI1Ck_93M``E(`d;d;u3z@3`0KCDFPvZS zf&cH+uO@wPc&3<}oBO@=aW~#}ciGyz(=VHvJvDps#4GOh-%k0DDbuniPs_e6EAW6$ z^l(XS_cR-yZg?hYBBk`q|0P|McHq z{CSS`FWU4)E&SJy=KPCF`N3v$|3!fTtnUmEH;B8&aAQFBK9da?F8W#Y^HFnSUx}=; zwnjb`@w9ha_%mTY^=vnvHJ+2k87p?toWK6%J0J$}3pwkiJ>tCIcS0Vqy%Fd*z!YenI(6uC@Ghpea9}kQD^vM<#Qje}1PL zxHOq0LVqj$g73<;*c8mp>6A%5{MBx=*G!!pD!5GCzI5rG#pO#EuUWDpdu92W#o3d^w^}J)vvkSo|Fd*W_MMB@ ztmV<@+eK;ds>Sy%T{F4(wv}tkxznA|z^Y}%YnPUKNNonzt6{W?? z?_Rv-c5%njJIa@?4K}@OCBqpQ*guqAws_^LU>SeAf$sl7_P4%*(t+0QW*Cd_Uc2HS zq$|r-t}VWE#p3d%ez^WTc>uNP;P`FMoDRrmgvcW<8z`TKJbB>K|2H}yPee|h`nO+v zh&*Vkq!4*}pvI68d0-e~Scu#Slur+lF9^s-hsYNN1M=}9qMhu(lZln;xh{k@%U^WpY@{5#_b$V10-DB$mUJUa0U{(1xQ(D8g7 zkcW;(TscrPv|mp^9@_7!fL!;R8tjzwL*J68eM|ntx7yb{ekdK{FQ+G zxe)oGfP8m|e0@OvVn80`XRzO2hLry(Q2v#W@;d|aS3}C54#*FNln;*ccOm7^1Le8KM9ei2jqVak+UD;|J8-aoq+sYKpw;`h)-{b z{B&SEqrW$B5iB3ncU*`(sFxEW4~~Cch&(v{AB4z*<6jjb503wqSp(w<<^K-?@=*Ri z7m$bg(XfDiVc??I3&G#yz(r6V)K}+;`WKX+4&eFdfcS6nV4k!#@H|*PIIlkxA`i~% zBD?(FJJv3~jq}O_8E->={w^KURv?`&Qn}baM zb_Q@CxchESqGO1M!wD7$oSgvBje!(WuTOM8!L)NopxoGiJTy~29nx>0xWA3ywLVYa z&~5&|*4NXH9v6E`|L_a;zaU`e;KTU9MesbEdtKz)+|xxSU#;1^lf8k$XV0!M?d0c3H-Darm5s4wW`mYe9 bL&hI+D+W>KpVdDY82@U2D}VnJTL1q8o@kmo literal 0 HcmV?d00001 diff --git a/applications/main/subghz/scenes/subghz_scene_hitag2_bf.c b/applications/main/subghz/scenes/subghz_scene_hitag2_bf.c index b9730d90..f3108792 100644 --- a/applications/main/subghz/scenes/subghz_scene_hitag2_bf.c +++ b/applications/main/subghz/scenes/subghz_scene_hitag2_bf.c @@ -13,6 +13,7 @@ #include #include #include +#include #define TAG "SubGhzSceneHitag2Bf" @@ -21,6 +22,16 @@ // Maximum files to scan in the same directory when auto-loading captures #define HITAG2_BF_MAX_SCAN_FILES 64U +// --- BLE compute-offload wire protocol (must match qUnleashed exactly) --- +#define HT_MSG_BF_REQUEST 0x20 // Flipper -> phone +#define HT_MSG_BF_PROGRESS 0x21 // phone -> Flipper +#define HT_MSG_BF_RESULT 0x22 // phone -> Flipper +#define HT_MSG_BF_CANCEL 0x23 // Flipper -> phone + +// Cap offloaded captures so the request fits BLE_SVC_SERIAL_CUSTOM_DATA_LEN_MAX +// (64). Request = 14-byte header + 7 bytes/capture -> 7 captures = 63 bytes. +#define HT_MSG_BF_MAX_OFFLOAD_CAPTURES 7U + typedef struct { SubGhz* subghz; FuriThread* thread; @@ -32,6 +43,7 @@ typedef struct { uint8_t found_key[6]; uint32_t found_epoch; uint8_t found_level; + bool ble_offload; } Hitag2BfCtx; // ----------------------------------------------------------------------------- @@ -176,6 +188,72 @@ static void hitag2_bf_write_key_to_fff(Hitag2BfCtx* ctx) { real_fff, "Hitag2 Epoch", &ctx->found_epoch, 1); } +// Append the recovered key to the known-keys dictionary at +// /ext/subghz/assets/hitag2 so the list grows over time and future attacks hit +// it at L1/L3 instantly. Format: one 12-hex-char line per key (no spaces), +// matching the file's convention. Deduplicates by scanning existing lines +// first. Best-effort: any storage failure is silently ignored (the key is +// already saved in the .sub). Used for BOTH local and offloaded finds. +static void hitag2_bf_append_key_to_dict(Hitag2BfCtx* ctx) { + char key_hex[13]; + snprintf( + key_hex, + sizeof(key_hex), + "%02X%02X%02X%02X%02X%02X", + ctx->found_key[0], + ctx->found_key[1], + ctx->found_key[2], + ctx->found_key[3], + ctx->found_key[4], + ctx->found_key[5]); + + Storage* storage = furi_record_open(RECORD_STORAGE); + + // Ensure the assets directory exists (mirrors the keeloq keystore pattern). + storage_simply_mkdir(storage, EXT_PATH("subghz/assets")); + + const char* path = EXT_PATH("subghz/assets/hitag2"); + + // Dedup: scan the existing file for this key (case-insensitive-ish; the + // file uses uppercase, and we write uppercase, so a plain substring match + // over the whole content is sufficient and cheap for a small dictionary). + bool already_present = false; + File* rf = storage_file_alloc(storage); + if(storage_file_open(rf, path, FSAM_READ, FSOM_OPEN_EXISTING)) { + char buf[256]; + FuriString* content = furi_string_alloc(); + size_t n; + while((n = storage_file_read(rf, buf, sizeof(buf))) > 0) { + furi_string_cat_str(content, ""); // ensure alloc + for(size_t i = 0; i < n; i++) { + furi_string_push_back(content, buf[i]); + } + } + if(furi_string_search_str(content, key_hex, 0) != FURI_STRING_FAILURE) { + already_present = true; + } + furi_string_free(content); + } + storage_file_close(rf); + storage_file_free(rf); + + if(!already_present) { + File* wf = storage_file_alloc(storage); + // Open for append (create if missing). + if(storage_file_open(wf, path, FSAM_WRITE, FSOM_OPEN_APPEND)) { + char line[16]; + int len = snprintf(line, sizeof(line), "%s\n", key_hex); + if(len > 0) { + storage_file_write(wf, line, (size_t)len); + } + } + storage_file_close(wf); + storage_file_free(wf); + } + + furi_record_close(RECORD_STORAGE); +} + // ----------------------------------------------------------------------------- // Progress callback (from worker thread) // ----------------------------------------------------------------------------- @@ -220,6 +298,108 @@ static bool hitag2_bf_progress_cb( return true; } +// ----------------------------------------------------------------------------- +// BLE compute-offload (Flipper offloads the heavy attack to a connected phone) +// ----------------------------------------------------------------------------- + +static void hitag2_ble_data_received(uint8_t* data, uint16_t size, void* context) { + Hitag2BfCtx* ctx = context; + if(size < 1 || ctx->cancel) return; + + if(data[0] == HT_MSG_BF_PROGRESS && size >= 10) { + uint8_t pct = data[1]; + uint64_t slots_done = 0; + memcpy(&slots_done, data + 2, 8); + + uint32_t elapsed_sec = (furi_get_tick() - ctx->start_tick) / 1000U; + + subghz_view_hitag2_bf_update_stats( + ctx->subghz->subghz_hitag2_bf, + SubGhzHitag2BfLevelHitag2Hell, + "H2H", + pct, + slots_done, + 0, + elapsed_sec, + 0, + subghz_hitag2_bf_get_capture_count(ctx->bf)); + + } else if(data[0] == HT_MSG_BF_RESULT && size >= 12) { + uint8_t found = data[1]; + + if(found) { + memcpy(ctx->found_key, data + 2, 6); + memcpy(&ctx->found_epoch, data + 8, 4); + ctx->found_level = SubGhzHitag2BfLevelHitag2Hell; + ctx->success = true; + } + + view_dispatcher_send_custom_event( + ctx->subghz->view_dispatcher, HITAG2_BF_EVENT_DONE); + } +} + +static void hitag2_ble_cleanup(Hitag2BfCtx* ctx) { + if(!ctx->ble_offload) return; + Bt* bt = furi_record_open(RECORD_BT); + bt_set_custom_data_callback(bt, NULL, NULL); + furi_record_close(RECORD_BT); + ctx->ble_offload = false; +} + +static bool hitag2_ble_start_offload(Hitag2BfCtx* ctx) { + Bt* bt = furi_record_open(RECORD_BT); + if(!bt_is_connected(bt)) { + furi_record_close(RECORD_BT); + return false; + } + + // Register callback for incoming data (progress/result) + bt_set_custom_data_callback(bt, hitag2_ble_data_received, ctx); + + // Build the BF request from the captures already loaded in ctx. + // Cap at HT_MSG_BF_MAX_OFFLOAD_CAPTURES to stay within the 64-byte limit. + uint8_t cap_total = subghz_hitag2_bf_get_capture_count(ctx->bf); + uint8_t cap_count = (cap_total > HT_MSG_BF_MAX_OFFLOAD_CAPTURES) ? + (uint8_t)HT_MSG_BF_MAX_OFFLOAD_CAPTURES : + cap_total; + + uint32_t uid = subghz_hitag2_bf_get_uid(ctx->bf); + uint32_t l0_start = 0; + uint32_t l0_end = 0; + + uint8_t req[BLE_SVC_SERIAL_CUSTOM_DATA_LEN_MAX]; + uint16_t off = 0; + req[off++] = HT_MSG_BF_REQUEST; // [0] + memcpy(req + off, &uid, 4); // [1..4] uid LE + off += 4; + memcpy(req + off, &l0_start, 4); // [5..8] l0_start LE + off += 4; + memcpy(req + off, &l0_end, 4); // [9..12] l0_end LE + off += 4; + req[off++] = cap_count; // [13] capture_count + + for(uint8_t i = 0; i < cap_count; i++) { + uint16_t control = 0; + uint8_t button = 0; + uint32_t hop = 0; + subghz_hitag2_bf_get_capture(ctx->bf, i, NULL, &control, &button, &hop); + req[off++] = button; // btn:1 + memcpy(req + off, &control, 2); // cnt:2 LE + off += 2; + memcpy(req + off, &hop, 4); // hop:4 LE + off += 4; + } + + bt_custom_data_tx(bt, req, off); + + furi_record_close(RECORD_BT); + ctx->ble_offload = true; + return true; +} + +// ----------------------------------------------------------------------------- + static int32_t hitag2_bf_thread(void* context) { Hitag2BfCtx* ctx = context; @@ -293,8 +473,13 @@ void subghz_scene_hitag2_bf_on_enter(void* context) { view_dispatcher_switch_to_view(subghz->view_dispatcher, SubGhzViewIdHitag2Bf); ctx->start_tick = furi_get_tick(); - ctx->thread = furi_thread_alloc_ex("Hitag2BF", 4096, hitag2_bf_thread, ctx); - furi_thread_start(ctx->thread); + + // Try BLE offload first, fall back to the local worker thread if no phone + // is connected. + if(!hitag2_ble_start_offload(ctx)) { + ctx->thread = furi_thread_alloc_ex("Hitag2BF", 4096, hitag2_bf_thread, ctx); + furi_thread_start(ctx->thread); + } } bool subghz_scene_hitag2_bf_on_event(void* context, SceneManagerEvent event) { @@ -305,6 +490,7 @@ bool subghz_scene_hitag2_bf_on_event(void* context, SceneManagerEvent event) { if(event.type == SceneManagerEventTypeCustom) { if(event.event == HITAG2_BF_EVENT_DONE) { + hitag2_ble_cleanup(ctx); if(ctx->thread) { furi_thread_join(ctx->thread); furi_thread_free(ctx->thread); @@ -312,8 +498,12 @@ bool subghz_scene_hitag2_bf_on_event(void* context, SceneManagerEvent event) { } if(ctx->success) { - // Persist key into the file + // Persist key into the .sub file hitag2_bf_write_key_to_fff(ctx); + // ...and grow the known-keys dictionary so it's found instantly + // next time. Fires for both local finds and offloaded (phone) + // finds, since both converge on this DONE handler. + hitag2_bf_append_key_to_dict(ctx); subghz_save_protocol_to_file( subghz, subghz_txrx_get_fff_data(subghz->txrx), @@ -366,6 +556,14 @@ bool subghz_scene_hitag2_bf_on_event(void* context, SceneManagerEvent event) { return true; } else if(event.event == SubGhzCustomEventViewTransmitterBack) { + if(ctx->ble_offload) { + // Tell the phone to stop the offloaded attack + Bt* bt = furi_record_open(RECORD_BT); + uint8_t cancel_msg = HT_MSG_BF_CANCEL; + bt_custom_data_tx(bt, &cancel_msg, 1); + furi_record_close(RECORD_BT); + hitag2_ble_cleanup(ctx); + } if(ctx->thread) { ctx->cancel = true; furi_thread_join(ctx->thread); @@ -390,6 +588,7 @@ void subghz_scene_hitag2_bf_on_exit(void* context) { subghz->scene_manager, SubGhzSceneHitag2Bf); if(ctx) { + hitag2_ble_cleanup(ctx); if(ctx->thread) { ctx->cancel = true; furi_thread_join(ctx->thread);