435 lines
12 KiB
C
435 lines
12 KiB
C
/** @file
|
|
Security+ 2.0 rolling code.
|
|
|
|
Copyright (C) 2020 Peter Shipley <peter.shipley@gmail.com>
|
|
Based on code by Clayton Smith https://github.com/argilo/secplus
|
|
|
|
This program is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
(at your option) any later version.
|
|
*/
|
|
|
|
/**
|
|
Freq 310, 315 and 390 MHz.
|
|
|
|
Security+ 2.0 is described in [US patent application US20110317835A1](https://patents.google.com/patent/US20110317835A1/)
|
|
|
|
|
|
*/
|
|
|
|
#include "decoder.h"
|
|
|
|
/**
|
|
Security+ 2.0 rolling code.
|
|
|
|
Data comes in two bursts/packets.
|
|
|
|
Layout:
|
|
|
|
bits = `AA BB IIII OOOO X*30`
|
|
|
|
- AA = payload type ( 2 bits 00 or 01 )
|
|
- BB = FrameID ( 2 bits always 00)
|
|
- IIII = inversion indicator ( 4 bits )
|
|
- OOOO = Order indicator ( 4 bits ).
|
|
- XXXX.... = data ( 30 bits )
|
|
|
|
---
|
|
|
|
data is broken up into 3 parts ( p0 p1 p2 )
|
|
eg:
|
|
|
|
data = `ABCABCABCABCABCABCABCABCABCABC`
|
|
becomes:
|
|
|
|
`p0 = AAAAAAAAAA`
|
|
`p1 = BBBBBBBBBB`
|
|
`p2 = CCCCCCCCCC`
|
|
|
|
these three parts are then inverted and reordered based on the 4bit Order and Inversion indicators
|
|
|
|
fixed generated from concatenate p0 + p1
|
|
|
|
roll_array is generated from the 8 bit used for Order and Inversion indicators + p3
|
|
by reading the buffer in binary bit pairs forming trinary values
|
|
|
|
EG:
|
|
`1 0 0 1 1 0 1 0 0 1 1 0=> [1 0] [0 1] [1 0] [1 0] [0 1] [1 0] => 2 1 2 2 1 2`
|
|
|
|
Returns data in :
|
|
* roll_array as an array of trinary values ( 0, 1, 2) the value 3 is invalid
|
|
* fixed_p as an bitbuffer_t with 20 bits of data
|
|
|
|
|
|
Once the above has been run twice the two are merged
|
|
|
|
---
|
|
|
|
*/
|
|
|
|
int _decode_v2_half(bitbuffer_t *bits, uint8_t roll_array[], bitbuffer_t *fixed_p, int verbose)
|
|
{
|
|
uint8_t invert = 0;
|
|
uint8_t order = 0;
|
|
uint32_t x = 0;
|
|
unsigned int start_pos = 2; //
|
|
uint8_t buffy[10];
|
|
|
|
|
|
uint8_t part_id = (bits->bb[0][0] >> 6);
|
|
|
|
// fprintf(stderr, "%s: part %d\n", __func__, part_id);
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "%s: bits_per_row = %d\n", __func__, bits->bits_per_row[0]);
|
|
|
|
bitrow_debugf(bits->bb[0], bits->bits_per_row[0], "%s : ", __func__);
|
|
}
|
|
|
|
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 2);
|
|
start_pos += 2;
|
|
|
|
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 8);
|
|
start_pos += 8;
|
|
order = buffy[0] >> 4;
|
|
|
|
invert = buffy[0] & 0x0f;
|
|
// bitrow_debug(&invert, 8);
|
|
|
|
bitbuffer_extract_bytes(bits, 0, start_pos, buffy, 30);
|
|
start_pos += 30;
|
|
|
|
// copy 30 bits of data into 32bit int then shift >> 2
|
|
// memcpy(&dat, buffy, 4);
|
|
x = ((uint32_t)buffy[0] << 24) | (buffy[1] << 16) | (buffy[2] << 8) | (buffy[3]);
|
|
|
|
x >>= 2;
|
|
|
|
// using short to store 10bit values
|
|
uint16_t p0 = 0, p1 = 0, p2 = 0;
|
|
|
|
// sort 30 bits of interleaved data into three 10 bit buffers
|
|
for (int i = 0; i < 10; i++) {
|
|
p2 ^= (x & 0x00000001) << i; // 9-
|
|
x >>= 1;
|
|
p1 ^= (x & 0x00000001) << i;
|
|
x >>= 1;
|
|
p0 ^= (x & 0x00000001) << i;
|
|
x >>= 1;
|
|
}
|
|
|
|
// fprintf(stderr, "f1 (%d) %d %d %d\n", part_id, p0, p1, p2);
|
|
|
|
// selectively invert buffers
|
|
switch (invert) {
|
|
case 0x00: // 0b0000 (True, True, False),
|
|
p0 = ~p0 & 0x03FF;
|
|
p1 = ~p1 & 0x03FF;
|
|
break;
|
|
case 0x01: // 0b0001 (False, True, False),
|
|
p1 = ~p1 & 0x03FF;
|
|
break;
|
|
case 0x02: // 0b0010 (False, False, True),
|
|
p2 = ~p2 & 0x03FF;
|
|
break;
|
|
case 0x04: // 0b0100 (True, True, True),
|
|
p0 = ~p0 & 0x03FF;
|
|
p1 = ~p1 & 0x03FF;
|
|
p2 = ~p2 & 0x03FF;
|
|
break;
|
|
case 0x05: // 0b0101 (True, False, True),
|
|
case 0x0a: // 0b1010 (True, False, True),
|
|
p0 = ~p0 & 0x03FF;
|
|
p2 = ~p2 & 0x03FF;
|
|
break;
|
|
case 0x06: // 0b0110 (False, True, True),
|
|
p1 = ~p1 & 0x03FF;
|
|
p2 = ~p2 & 0x03FF;
|
|
break;
|
|
case 0x08: // 0b1000 (True, False, False),
|
|
p0 = ~p0 & 0x03FF;
|
|
break;
|
|
case 0x09: // 0b1001 (False, False, False),
|
|
break;
|
|
default:
|
|
if (verbose)
|
|
fprintf(stderr, "Invert FAIL\n");
|
|
return 1;
|
|
}
|
|
|
|
uint16_t a = p0, b = p1, c = p2;
|
|
|
|
// selectively reorder buffers
|
|
switch (order) {
|
|
case 0x06: // 0b0110 2, 1, 0],
|
|
case 0x09: // 0b1001 2, 1, 0],
|
|
p2 = a;
|
|
p1 = b;
|
|
p0 = c;
|
|
break;
|
|
|
|
case 0x08: // 0b1000 1, 2, 0],
|
|
case 0x04: // 0b0100 1, 2, 0],
|
|
p1 = a;
|
|
p2 = b;
|
|
p0 = c;
|
|
break;
|
|
|
|
case 0x01: // 0b0001 2, 0, 1],
|
|
p2 = a;
|
|
p0 = b;
|
|
p1 = c;
|
|
break;
|
|
|
|
case 0x00: // 0b0000 0, 2, 1],
|
|
p0 = a;
|
|
p2 = b;
|
|
p1 = c;
|
|
break;
|
|
|
|
case 0x05: // 0b0101 1, 0, 2],
|
|
p1 = a;
|
|
p0 = b;
|
|
p2 = c;
|
|
break;
|
|
|
|
case 0x02: // 0b0010 0, 1, 2],
|
|
case 0x0A: // 0b1010 0, 1, 2],
|
|
p0 = a;
|
|
p1 = b;
|
|
p2 = c;
|
|
break;
|
|
|
|
default:
|
|
if (verbose)
|
|
fprintf(stderr, "Order FAIL");
|
|
return 2;
|
|
}
|
|
|
|
bitbuffer_extract_bytes(bits, 0, 4, buffy, 8);
|
|
x = buffy[0];
|
|
int k = 0;
|
|
for (int i = 6; i >= 0; i -= 2) {
|
|
roll_array[k++] = (x >> i) & 0x03;
|
|
}
|
|
|
|
// bitrow_printf(buffy, 8, "%s ; buffy bits ", __func__);
|
|
|
|
// assemble binary bits into trinary
|
|
x = p2;
|
|
for (int i = 8; i >= 0; i -= 2) {
|
|
roll_array[k++] = (x >> i) & 0x03;
|
|
}
|
|
|
|
if (verbose) {
|
|
fprintf(stderr, "%s : roll_array : (%d) ", __func__, part_id);
|
|
for (int i = 0; i < 9; i++) {
|
|
fprintf(stderr, "%d ", roll_array[i]);
|
|
}
|
|
fprintf(stderr, "\n");
|
|
}
|
|
|
|
// SANITY check trinary valuse, 00/01/10 are valid, 11 is not
|
|
for (int i = 0; i < 9; i++) {
|
|
if (roll_array[i] == 3) {
|
|
fprintf(stderr, "roll_array val FAIL\n");
|
|
return 1; // DECODE_FAIL_SANITY;
|
|
}
|
|
}
|
|
|
|
// fixed_p = p0 + p1
|
|
for (int i = 9; i >= 0; i--) {
|
|
bitbuffer_add_bit(fixed_p, (p0 >> i) & 0x01);
|
|
}
|
|
for (int i = 9; i >= 0; i--) {
|
|
bitbuffer_add_bit(fixed_p, (p1 >> i) & 0x01);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const uint8_t _preamble[] = {0xaa, 0xaa, 0x95, 0x60};
|
|
unsigned _preamble_len = 28;
|
|
|
|
static int secplus_v2_callback(r_device *decoder, bitbuffer_t *bitbuffer)
|
|
{
|
|
unsigned search_index = 0;
|
|
bitbuffer_t bits = {0};
|
|
// int i = 0;
|
|
|
|
//bitbuffer_t bits_1 = {0};
|
|
bitbuffer_t fixed_1 = {0};
|
|
uint8_t rolling_1[16] = {0};
|
|
|
|
//bitbuffer_t bits_2 = {0};
|
|
bitbuffer_t fixed_2 = {0};
|
|
uint8_t rolling_2[16] = {0};
|
|
|
|
for (uint16_t row = 0; row < bitbuffer->num_rows; ++row) {
|
|
if (bitbuffer->bits_per_row[row] < 110) {
|
|
continue;
|
|
}
|
|
|
|
search_index = bitbuffer_search(bitbuffer, row, 0, _preamble, _preamble_len);
|
|
|
|
if (search_index >= bitbuffer->bits_per_row[row]) {
|
|
break;
|
|
}
|
|
|
|
bitbuffer_clear(&bits);
|
|
bitbuffer_manchester_decode(bitbuffer, row, search_index + 26, &bits, 80);
|
|
search_index += 20;
|
|
if (bits.bits_per_row[0] < 42) {
|
|
continue; // DECODE_ABORT_LENGTH;
|
|
}
|
|
|
|
if (decoder->verbose) {
|
|
(void)fprintf(stderr, "%s: manchester_decode %d len = %u\n", __func__,
|
|
0, bits.bits_per_row[0]);
|
|
bitrow_debugf(bits.bb[0], bits.bits_per_row[0], "%s: manchester_decoded %d", __func__, 0);
|
|
}
|
|
|
|
// valid = 0X00XXXX
|
|
// 1st 3rs and 4th bits should always be 0
|
|
if (bits.bb[0][0] & 0xB0) {
|
|
if (decoder->verbose)
|
|
fprintf(stderr, "%s: DECODE_FAIL_SANITY\n", __func__);
|
|
continue;
|
|
}
|
|
|
|
// 2nd bit indicates with half of the data
|
|
if (bits.bb[0][0] & 0xC0) {
|
|
if (decoder->verbose)
|
|
(void)fprintf(stderr, "%s: Set 2\n", __func__);
|
|
_decode_v2_half(&bits, rolling_2, &fixed_2, decoder->verbose);
|
|
}
|
|
else {
|
|
if (decoder->verbose)
|
|
(void)fprintf(stderr, "%s: Set 1\n", __func__);
|
|
_decode_v2_half(&bits, rolling_1, &fixed_1, decoder->verbose);
|
|
}
|
|
|
|
// break if we've received both halfs
|
|
if (fixed_1.bits_per_row[0] > 1 && fixed_2.bits_per_row[0] > 1) {
|
|
break;
|
|
}
|
|
|
|
}
|
|
|
|
// Do was have what we need ??
|
|
if (fixed_1.bits_per_row[0] == 0 || fixed_2.bits_per_row[0] == 0) {
|
|
// No? Awww F'ck it then
|
|
if (decoder->verbose)
|
|
fprintf(stderr, "%s: DECODE_FAIL_SANITY\n", __func__);
|
|
return DECODE_FAIL_SANITY;
|
|
}
|
|
|
|
// Assemble rolling_1[] and rolling_2[] into rolling_digits[]
|
|
uint8_t rolling_digits[24] = {0};
|
|
uint8_t *r;
|
|
|
|
r = rolling_digits;
|
|
*r++ = rolling_2[8];
|
|
*r++ = rolling_1[8];
|
|
for (int i = 4; i < 8; i++) {
|
|
*r++ = rolling_2[i];
|
|
}
|
|
for (int i = 4; i < 8; i++) {
|
|
*r++ = rolling_1[i];
|
|
}
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
*r++ = rolling_2[i];
|
|
}
|
|
for (int i = 0; i < 4; i++) {
|
|
*r++ = rolling_1[i];
|
|
}
|
|
|
|
// compute rolling_total from rolling_digits[]
|
|
uint32_t rolling_total = 0;
|
|
uint32_t rolling_temp = 0;
|
|
|
|
for (int i = 0; i < 18; i++) {
|
|
rolling_temp = (rolling_temp * 3) + rolling_digits[i];
|
|
// fprintf(stderr, ">> %12d\t%d\n", rolling_temp, rolling_digits[i]);
|
|
}
|
|
|
|
// Max value = 2^28 (268435456)
|
|
if ( rolling_temp >= 0x10000000 ) {
|
|
return DECODE_FAIL_SANITY;
|
|
}
|
|
|
|
// value is 28 bits thus need to shift over 4 bit
|
|
rolling_total = reverse32(rolling_temp);
|
|
rolling_total = rolling_total >> 4;
|
|
|
|
|
|
// Assemble "fixed" data part
|
|
uint64_t fixed_total = 0;
|
|
uint8_t *bb;
|
|
bb = fixed_1.bb[0];
|
|
fixed_total ^= ((uint64_t)bb[0]) << 32;
|
|
fixed_total ^= ((uint64_t)bb[1]) << 24;
|
|
fixed_total ^= ((uint64_t)bb[2]) << 16;
|
|
|
|
bb = fixed_2.bb[0];
|
|
fixed_total ^= ((uint64_t)bb[0]) << 12;
|
|
fixed_total ^= ((uint64_t)bb[1]) << 4;
|
|
fixed_total ^= (bb[2] >> 4) & 0x0f;
|
|
|
|
// fprintf(stderr, "fixed_total = %lu\n", fixed_total);
|
|
|
|
// int button = fixed_total >> 32;
|
|
// int remote_id = fixed_total & 0xffffffff;
|
|
char fixed_str[16];
|
|
char rolling_str[16];
|
|
|
|
// rolling_total is a 28 bit unsigned number
|
|
// fixed_totals is 40 bit in a uint64_t
|
|
snprintf(fixed_str, sizeof(fixed_str), "%llu", (long long unsigned)fixed_total);
|
|
snprintf(rolling_str, sizeof(rolling_str), "%u", rolling_total);
|
|
|
|
/* clang-format off */
|
|
data_t *data;
|
|
data = data_make(
|
|
"model", "Model", DATA_STRING, "Secplus-v2",
|
|
"id", "", DATA_INT, (fixed_total & 0xffffffff),
|
|
"button_id", "Button-ID", DATA_INT, (fixed_total >> 32),
|
|
"remote_id", "Remote-ID", DATA_INT, (fixed_total & 0xffffffff),
|
|
// "fixed", "", DATA_INT, fixed_total,
|
|
"fixed", "Fixed_Code", DATA_STRING, fixed_str,
|
|
"rolling", "Rolling_Code", DATA_STRING, rolling_str,
|
|
NULL);
|
|
/* clang-format on */
|
|
|
|
decoder_output_data(decoder, data);
|
|
return 1;
|
|
}
|
|
|
|
static char *output_fields[] = {
|
|
// Common fields
|
|
"model",
|
|
"id",
|
|
"rolling"
|
|
"fixed",
|
|
"button_id",
|
|
"remote_id",
|
|
NULL,
|
|
};
|
|
|
|
// Freq 310.01M
|
|
// -X "n=vI3,m=OOK_PCM,s=230,l=230,t=40,r=10000,g=7400,match={24}0xaaaa9560"
|
|
|
|
r_device secplus_v2 = {
|
|
.name = "Security+ 2.0 (Keyfob)",
|
|
.modulation = OOK_PULSE_PCM_RZ,
|
|
.short_width = 250,
|
|
.long_width = 250,
|
|
.tolerance = 50,
|
|
.gap_limit = 1500,
|
|
.reset_limit = 9000,
|
|
.decode_fn = &secplus_v2_callback,
|
|
.disabled = 0,
|
|
.fields = output_fields,
|
|
};
|