keeloq.c 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. /* Copyright (C) 2022-2023 Salvatore Sanfilippo -- All Rights Reserved
  2. * See the LICENSE file for information about the license.
  3. *
  4. * Microchip HCS200/HCS300/HSC301 KeeLoq, rolling code remotes.
  5. *
  6. * Usually 443.92 Mhz OOK, ~200us or ~400us pulse len, depending
  7. * on the configuration.
  8. *
  9. * Preamble: 12 pairs of alternating pulse/gap.
  10. * Sync: long gap of around 10 times the duration of the short-pulse.
  11. * Data: pulse width encoded data. Each bit takes three cycles:
  12. *
  13. * 0 = 110
  14. * 1 = 100
  15. *
  16. * There are a total of 66 bits transmitted.
  17. * 0..31: 32 bits of encrypted rolling code.
  18. * 32..59: Remote ID, 28 bits
  19. * 60..63: Buttons pressed
  20. * 64..64: Low battery if set
  21. * 65..65: Always set to 1
  22. *
  23. * Bits in bytes are inverted: least significant bit is first.
  24. * For some reason there is no checksum whatsoever, so we only decode
  25. * if we find everything well formed.
  26. */
  27. #include "../app.h"
  28. static bool decode(uint8_t *bits, uint32_t numbytes, uint32_t numbits, ProtoViewMsgInfo *info) {
  29. /* In the sync pattern, we require the 12 high/low pulses and at least
  30. * half the gap we expect (5 pulses times, one is the final zero in the
  31. * 24 symbols high/low sequence, then other 4). */
  32. const char *sync_pattern = "101010101010101010101010" "0000";
  33. uint8_t sync_len = 24+4;
  34. if (numbits-sync_len+sync_len < 3*66) return false;
  35. uint32_t off = bitmap_seek_bits(bits,numbytes,0,numbits,sync_pattern);
  36. if (off == BITMAP_SEEK_NOT_FOUND) return false;
  37. info->start_off = off;
  38. off += sync_len; // Seek start of message.
  39. /* Now there is half the gap left, but we allow from 3 to 7, instead of 5
  40. * symbols of gap, to avoid missing the signal for a matter of wrong
  41. * timing. */
  42. uint8_t gap_len = 0;
  43. while(gap_len <= 7 && bitmap_get(bits,numbytes,off+gap_len) == 0)
  44. gap_len++;
  45. if (gap_len < 3 || gap_len > 7) return false;
  46. off += gap_len;
  47. FURI_LOG_E(TAG, "Keeloq preamble+sync found");
  48. uint8_t raw[9] = {0};
  49. uint32_t decoded =
  50. convert_from_line_code(raw,sizeof(raw),bits,numbytes,off,
  51. "110","100"); /* Pulse width modulation. */
  52. FURI_LOG_E(TAG, "Keeloq decoded bits: %lu", decoded);
  53. if (decoded < 66) return false; /* Require the full 66 bits. */
  54. info->pulses_count = (off+66*3) - info->start_off;
  55. bitmap_reverse_bytes_bits(raw,sizeof(raw)); /* Keeloq is LSB first. */
  56. int buttons = raw[7]>>4;
  57. int lowbat = (raw[8]&0x1) == 0; // Actual bit meaning: good battery level
  58. int alwaysone = (raw[8]&0x2) != 0;
  59. fieldset_add_bytes(info->fieldset,"encr",raw,8);
  60. raw[7] = raw[7]<<4; // Make ID bits contiguous
  61. fieldset_add_bytes(info->fieldset,"id",raw+4,7); // 28 bits, 7 nibbles
  62. fieldset_add_bin(info->fieldset,"s[2,1,0,3]",buttons,4);
  63. fieldset_add_bin(info->fieldset,"low battery",lowbat,1);
  64. fieldset_add_bin(info->fieldset,"always one",alwaysone,1);
  65. return true;
  66. }
  67. static void get_fields(ProtoViewFieldSet *fieldset) {
  68. uint8_t remote_id[4] = {0xab, 0xcd, 0xef, 0xa0};
  69. uint8_t encr[4] = {0xab, 0xab, 0xab, 0xab};
  70. fieldset_add_bytes(fieldset,"encr",encr,8);
  71. fieldset_add_bytes(fieldset,"id",remote_id,7);
  72. fieldset_add_bin(fieldset,"s[2,1,0,3]",2,4);
  73. fieldset_add_bin(fieldset,"low battery",0,1);
  74. fieldset_add_bin(fieldset,"always one",1,1);
  75. }
  76. static void build_message(RawSamplesBuffer *samples, ProtoViewFieldSet *fieldset)
  77. {
  78. uint32_t te = 380; // Short pulse duration in microseconds.
  79. // Sync: 12 pairs of pulse/gap + 9 times gap
  80. for (int j = 0; j < 12; j++) {
  81. raw_samples_add(samples,true,te);
  82. raw_samples_add(samples,false,te);
  83. }
  84. raw_samples_add(samples,false,te*9);
  85. // Data, 66 bits.
  86. uint8_t data[9] = {0};
  87. memcpy(data,fieldset->fields[0]->bytes,4); // Encrypted part.
  88. memcpy(data+4,fieldset->fields[1]->bytes,4); // ID.
  89. data[7] = data[7]>>4 | fieldset->fields[2]->uvalue << 4; // s[2,1,0,3]
  90. int low_battery = fieldset->fields[3] != 0;
  91. int always_one = fieldset->fields[4] != 0;
  92. low_battery = !low_battery; // Bit real meaning is good battery level.
  93. data[8] |= low_battery;
  94. data[8] |= (always_one << 1);
  95. bitmap_reverse_bytes_bits(data,sizeof(data)); /* Keeloq is LSB first. */
  96. for (int j = 0; j < 66; j++) {
  97. if (bitmap_get(data,9,j)) {
  98. raw_samples_add(samples,true,te);
  99. raw_samples_add(samples,false,te*2);
  100. } else {
  101. raw_samples_add(samples,true,te*2);
  102. raw_samples_add(samples,false,te);
  103. }
  104. }
  105. }
  106. ProtoViewDecoder KeeloqDecoder = {
  107. .name = "Keeloq",
  108. .decode = decode,
  109. .get_fields = get_fields,
  110. .build_message = build_message
  111. };