#include <stdio.h>

unsigned char checksum(const char* src_buffer) {
  // skip 73 55 header (and 55 footer is beyond checksum anyway)
  const char* table = &src_buffer[2];
  const char packet_len = (table[0] & 0x1F) + 0xb; // передается превышение длины внутреннего пакета

  const char generator = 0xA9;
  char crc = 0;
  for(const char* ptr = table; ptr < table + packet_len; ptr++){
    crc ^= *ptr;
    for (char bit = 8; bit > 0; bit--)
      if (crc & 0x80)
        crc = (crc << 1) ^ generator;
      else
        crc <<= 1;
  }
  return crc;
}

//--------------------------
// дальше тесты

#define KNRM  "\x1B[0m"
#define KRED  "\x1B[31m"
#define KGRN  "\x1B[32m"
#define KYEL  "\x1B[33m"
#define KBLU  "\x1B[34m"
#define KMAG  "\x1B[35m"
#define KCYN  "\x1B[36m"
#define KWHT  "\x1B[37m"

int test_success = 0, test_fail = 0;

void test_on(const char* packet, const char* label) {
  char len = packet[0]; const char* input = &packet[1];
  char expected = packet[packet[0] - 1];

  printf("len %d(x%02X)", len, len);
  printf(", expect %02x [%02x", expected&0xff, *input&0xff);
  for(int i = 1; i < len; i++) { printf(" %02x", input[i]&0xff); }
  printf("]: ");

  char sum = checksum(input);
  if(sum == expected) {
    printf(KGRN "OK %s" KNRM "\n", label);
    test_success++;
  } else {
    printf(KRED "FAIL %s" KNRM ": sum 0x%x != 0x%x\n", label, sum & 0xff, expected & 0xff);
    test_fail++;
  }
}

int main() {
  char example[] = {
    // 7F 10, - burst write 16 bytes
    0x0F, // packet len (aired) of the following wrapped packet
    0x73, 0x55, // preamble for uart
    0x20, // additional packet length in bits 0-4 (0x1F mask), base len 0xb, bit 5 (0x20) may be direction
    0x00, // unknown
    0x2F, 0x15, // target address
    0x09, 0xFF, // our radio address?
    0x01, // command num
    0x00, 0x00, 0x00, 0x00, // password
    // here go additional bytes, none in this example
    0x6A, // checksum byte (calculated for content after preamble)
    0x55 // footer
  };

  printf(KYEL "From forum text examples:"KNRM"\n");
  test_on(example, "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x2F\x15\x09\xFF\x01\x00\x00\x00\x00\x6A\x55"[2], "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x2E\x15\x09\xFF\x01\x00\x00\x00\x00\x28\x55"[2], "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x2D\x15\x09\xFF\x01\x00\x00\x00\x00\xEE\x55"[2], "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x00\x00\x09\xFF\x01\x00\x00\x00\x00\x82\x55"[2], "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x67\x2B\x09\xFF\x01\x00\x00\x00\x00\x1E\x55"[2], "");
  test_on(&"\x7F\x10\x0F" "\x73\x55\x20\x00\x9F\x86\x09\xFF\x01\x00\x00\x00\x00\xAF\x55"[2], "");

  printf(KYEL "\nFrom pic spi dump:"KNRM"\n");
  test_on("\x0f" "\x73\x55\x20\x00\xEA\x79\xFE\xFF\x01\x00\x00\x00\x00\x68\x55", "pic cmd1");
  test_on("\x0f" "\x73\x55\x20\x00\xEA\x79\xFE\xFF\x30\x00\x00\x00\x00\xAD\x55", "pic cmd30");
  test_on("\x0f" "\x73\x55\x20\x00\xEA\x79\xFE\xFF\x1c\x00\x00\x00\x00\xb7\x55", "pic cmd1c");

  test_on("\x10" "\x73\x55\x21\x00\xea\x79\xfe\xff\x05\x00\x00\x00\x00\x04\x0c\x55", "pic cmd5_4");
  test_on("\x10" "\x73\x55\x21\x00\xea\x79\xfe\xff\x05\x00\x00\x00\x00\x05\xa5\x55", "pic cmd5_5");
  test_on("\x10" "\x73\x55\x21\x00\xea\x79\xfe\xff\x2b\x00\x00\x00\x00\x00\xf4\x55", "pic cmd2b_0");
  test_on("\x0f" "\x73\x55\x20\x00\xEA\x79\xFE\xFF\x10\x00\x00\x00\x00\x3F\x55", "pic cmd10");

  printf(KYEL "\nFrom forum and images:"KNRM"\n");
  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\xFE\xFF\x01\x00\x00\x00\x00\xCE\x55", "dsmas forum pult");

  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\x09\xFF\x01\x00\x00\x00\x00\xDC\x55", "forum image cmd 1");
  test_on("\x13" "\x73\x55\x04\x00\x09\xff\x7c\x64\x01\xa8\x01\x5b\x00\x09\x05\x7c\x64\x9e\x55", "forum cmd 1 resp");

  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\x09\xFF\x30\x00\x00\x00\x00\x19\x55", "forum cmd30");
  test_on("\x2e" "\x73\x55\x1F\x00\x09\xFF\x7C\x64\x30\xA8\x01\x5B\x00\x21\x09\x05\x69\x96\x43\xBF\x00\x00\x86\x6A" "\x62\x00\x00\xC1\x2B\x3A\xAC\x1B\x99\xB9\x62\x00\x00\x00\x04\x01\x06\x00\x11\x0C\x13\x55", "forum cmd 30 resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\x09\xFF\x05\x00\x00\x00\x00\x04\x89\x55", "forum cmd5_4");
  test_on("\x2d" "\x73\x55\x1E\x00\x09\xFF\x7C\x64\x05\xA8\x01\x5B\x00\x04\x62\x01\x00\x01\x00\x97\x00\x00\x00\x97\x00\x00\x00\x97\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9F\x55", "forum cmd 5_4 resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\x09\xFF\x2B\x00\x00\x00\x00\x00\x71\x55", "forum cmd2b_0");


  // на скрине с мощностью 00 40 - чексумма с другого скрина похоже, с 0 сходится
  // test_on("\x2D" "\x73\x55\x1E\x00\x09\xFF\x7C\x64\x2B" "\xA8\x01\x5B\x00\x00\x01\x00\x01\x00\x00\x00\x40\x00\x00\x00"
  test_on("\x2D" "\x73\x55\x1E\x00\x09\xFF\x7C\x64\x2B" "\xA8\x01\x5B\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00"
                 "\x87\x13""\x0E\x00""\xC0\x5B\xB9\x5B\xCE\x5B""\x02\x00\x00\x02\x00\x00\x02\x00\x00""\xAF\x55", "forum cmd2b_0 resp");

  printf(KYEL "\nFrom uart dump:"KNRM"\n");
  // added len, but it's not in uart -> should not affect crc? or added internally?
  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\xFE\xFF\x01\x00\x00\x00\x00\xCE\x55", "uart cmd1");
  test_on("\x13" "\x73\x55\x04\x00\xFE\xFF\x7C\x64\x01\xA8\x05\x5B\x00\x09\x05\x7C\x64\xA7\x55", "uart cmd1 resp");

  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\xFE\xFF\x30\x00\x00\x00\x00\x0B\x55", "uart cmd30");
  test_on("\x2e" "\x73\x55\x1F\x00\xFE\xFF\x7C\x64\x30\xA8\x05\x5B\x00\x21\x09\x05\x69\x96\x38\xE7\x00\x00\xC3\x20\x63\x00\x00\xC1\x2B\x3A\xAC\x1B\xCB\x97\x63\x00\x00\x00\x04\x01\x06\x00\x00\x00\xD7\x55", "uart cmd30 resp");

  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\xFE\xFF\x1C\x00\x00\x00\x00\x11\x55", "uart cmd1c");
  test_on("\x16" "\x73\x55\x07\x00\xFE\xFF\x7C\x64\x1C\xA8\x05\x5B\x00\x00\x0F\x12\x06\x1B\x0B\x15\x84\x55", "uart cmd1c resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\xFE\xFF\x05\x00\x00\x00\x00\x04\xA7\x55", "uart cmd5_4");
  test_on("\x2d" "\x73\x55\x1E\x00\xFE\xFF\x7C\x64\x05\xA8\x05\x5B\x00\x04\x62\x01\x00\x01\x00\x99\x00\x00\x00\x99\x00\x00\x00\x99\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x8B\x55", "uart cmd5_4 resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\xFE\xFF\x05\x00\x00\x00\x00\x05\x0E\x55", "uart cmd5_5");
  test_on("\x2d" "\x73\x55\x1E\x00\xFE\xFF\x7C\x64\x05\xA8\x05\x5B\x00\x05\x62\x01\x00\x01\x00\x2A\x00\x00\x00\x2A\x00\x00\x00\x2A\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xED\x55", "uart cmd5_5 resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\xFE\xFF\x2B\x00\x00\x00\x00\x00\x5F\x55", "uart cmd2b_0");
  test_on("\x2d" "\x73\x55\x1E\x00\xFE\xFF\x7C\x64\x2B\xA8\x05\x5B\x00\x00\x01\x00\x01\x00\x00\x00\x00\x00\x00\x00\x86\x13\x0C\x00\x49\x5D\x42\x5D\x56\x5D\x02\x00\x00\x02\x00\x00\x02\x00\x00\x9B\x55", "uart cmd2b_0 resp");

  test_on("\x10" "\x73\x55\x21\x00\x7C\x64\xFE\xFF\x2B\x00\x00\x00\x00\x10\x8A\x55", "uart cmd2b_10");
  test_on("\x2d" "\x73\x55\x1E\x00\xFE\xFF\x7C\x64\x2B\xA8\x05\x5B\x00\x10\x00\x00\x00\x00\x07\x80\x0C\x00\x10\x80\x00\x00\x00\x00\x00\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x21\x4E\x55", "uart cmd2b_10 resp");

  test_on("\x0f" "\x73\x55\x20\x00\x7C\x64\xFE\xFF\x10\x00\x00\x00\x00\x99\x55", "uart cmd10");
  test_on("\x22" "\x73\x55\x13\x00\xFE\xFF\x7C\x64\x10\xA8\x05\x5B\x00\x62\x02\x0A\x00\xC0\xB2\x01\x01\x01\x0A\xA8\x01\x06\x00\x1B\x02\x04\x01\x06\x09\x55", "uart cmd10 resp");

  if(test_fail) {
    printf(KGRN "\nFAILED %d tests (%d succeded)" KNRM "\n", test_fail, test_success);
    return 1;
  } else if(test_success) {
    printf(KGRN "\nSUCCESS - %d succeded" KNRM "\n", test_success);
  }
  return 0;
}
