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 00000000..60c9b2d6 Binary files /dev/null and b/applications/main/subghz/helpers/subghz_hitag2_hell.o differ 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);