/******************************************************************************
 *
 * @file       packet_handler.c
 * @author     The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
 * @brief      Modem packet handling routines
 * @see        The GNU Public License (GPL) Version 3
 *
 *****************************************************************************/
/*
 * 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */


// ********

// We use 128-bit AES CBC encryption if encryption is enabled


// encrypted packet format
// 16-byte  CBC .. 1st byte must not be zero
//  4-byte  source id
//  4-byte  destination id
//  1-byte  packet type
//  1-byte  tx sequence value
//  1-byte  rx sequence value
//  1-byte  data size
//  4-byte  crc of entire packet not including CBC bytes


// unencrypted packet format
//  1-byte  null byte .. set to zero to indicate packet is not encrypted
//  4-byte  source id
//  4-byte  destination id
//  1-byte  packet type
//  1-byte  tx sequence value
//  1-byte  rx sequence value
//  1-byte  data size
//  4-byte  crc of entire packet not including the null byte

// ********

#include <string.h>	// memmove

#include "main.h"
#include "rfm22b.h"
#include "fifo_buffer.h"
#include "aes.h"
#include "crc.h"
#include "saved_settings.h"
#include "packet_handler.h"

#if defined(PIOS_COM_DEBUG)
//  #define PACKET_DEBUG
#endif

// *****************************************************************************

#define PH_FIFO_BUFFER_SIZE             2048    // FIFO buffer size

// *****************************************************************************

#define AES_BLOCK_SIZE                  16      // AES encryption does it in 16-byte blocks ONLY

// default aes 128-bit encryption key
const uint8_t default_aes_key[AES_BLOCK_SIZE] = {0x65, 0x3b, 0x71, 0x89, 0x4a, 0xf4, 0xc8, 0xcb, 0x18, 0xd4, 0x9b, 0x4d, 0x4a, 0xbe, 0xc8, 0x37};

// *****************************************************************************

#define RETRY_RECONNECT_COUNT           60      // if transmission retries this many times then reset the link to the remote modem

#define PACKET_TYPE_DATA_COMP_BIT       0x80    // data compressed bit. if set then the data in the packet is compressed
#define PACKET_TYPE_MASK                0x7f    // packet type mask

enum {
	PACKET_TYPE_NONE = 0,

	PACKET_TYPE_CONNECT,						// for requesting a connection
	PACKET_TYPE_CONNECT_ACK,					// ack

	PACKET_TYPE_DISCONNECT,						// to tell the other modem they cannot connect to us

	PACKET_TYPE_DATA,							// data packet (packet contains user data)
	PACKET_TYPE_DATA_ACK,						// ack

	PACKET_TYPE_READY,							// tells the other modem we are ready to accept more data
	PACKET_TYPE_READY_ACK,						// ack

	PACKET_TYPE_NOTREADY,						// tells the other modem we're not ready to accept more data - we can also send user data in this packet type
	PACKET_TYPE_NOTREADY_ACK,					// ack

	PACKET_TYPE_DATARATE,						// for changing the RF data rate
	PACKET_TYPE_DATARATE_ACK,					// ack

	PACKET_TYPE_PING,							// used to check link is still up
	PACKET_TYPE_PONG,							// ack

	PACKET_TYPE_ADJUST_TX_PWR,					// used to ask the other modem to adjust it's tx power
	PACKET_TYPE_ADJUST_TX_PWR_ACK				// ack
};

#define BROADCAST_ADDR                          0xffffffff

//#pragma pack(push)
//#pragma pack(1)

typedef struct
{
    uint32_t            source_id;
    uint32_t            destination_id;
    uint8_t             type;
    uint8_t             tx_seq;
    uint8_t             rx_seq;
    uint8_t             data_size;
    uint32_t            crc;
} __attribute__((__packed__)) t_packet_header;

// this structure must be a multiple of 'AES_BLOCK_SIZE' bytes in size and no more than 255 bytes in size
typedef struct
{
    uint8_t             cbc[AES_BLOCK_SIZE];            // AES encryption Cipher-Block-Chaining key .. 1st byte must not be zero - to indicate the packet is encrypted
    t_packet_header     header;
    uint8_t             data[240 - sizeof(t_packet_header) - AES_BLOCK_SIZE];
} __attribute__((__packed__)) t_encrypted_packet;

// this structure must be no more than 255 bytes in size (255 = the maximum packet size)
typedef struct
{
    uint8_t             null_byte;                      // this must be set to zero - to indicate the packet is unencrypted
    t_packet_header     header;
    uint8_t             data[255 - sizeof(t_packet_header) - 1];
} __attribute__((__packed__)) t_unencrypted_packet;

//#pragma pack(pop)

// *****************************************************************************
// link state for each remote connection

enum {
	LINK_DISCONNECTED = 0,
	LINK_CONNECTING,
	LINK_CONNECTED
};

typedef struct
{
    uint32_t            serial_number;                  // their serial number

    uint8_t             tx_buffer[PH_FIFO_BUFFER_SIZE] __attribute__ ((aligned(4)));
    t_fifo_buffer       tx_fifo_buffer;                 // holds the data to be transmitted to the other modem

    uint8_t             rx_buffer[PH_FIFO_BUFFER_SIZE] __attribute__ ((aligned(4)));
    t_fifo_buffer       rx_fifo_buffer;                 // holds the data received from the other modem

    uint8_t             link_state;                     // holds our current RF link state

    uint8_t             tx_sequence;                    // incremented with each data packet transmitted, sent in every packet transmitted
    uint8_t             tx_sequence_data_size;          // the size of data we sent in our last packet

    uint8_t             rx_sequence;                    // incremented with each data packet received contain data, sent in every packet transmitted

    volatile uint16_t   tx_packet_timer;                // ms .. used for packet timing

    uint16_t            tx_retry_time_slots;            //       add's some random packet transmission timing - to try to prevent transmission collisions
    uint16_t            tx_retry_time_slot_len;         // ms ..   "                "                    "
    uint16_t            tx_retry_time;                  // ms ..   "                "                    "
    uint16_t            tx_retry_counter;               // incremented on each transmission, reset back to '0' when we receive an ack to our transmission

    volatile uint16_t   data_speed_timer;               // used for calculating the transmit/receive data rate
    volatile uint32_t   tx_data_speed_count;            // incremented with the number of data bits we send in our transmit packets
    volatile uint32_t   tx_data_speed;                  // holds the number of data bits we have sent each second
    volatile uint32_t   rx_data_speed_count;            // incremented with the number of data bits we send in our transmit packets
    volatile uint32_t   rx_data_speed;                  // holds the number of data bits we have received each second

    uint16_t            ping_time;                      // ping timer
    uint16_t            fast_ping_time;                 // ping timer
    bool                pinging;                        // TRUE if we are doing a ping test with the other modem - to check if it is still present

    bool                rx_not_ready_mode;              // TRUE if we have told the other modem we cannot receive data (due to buffer filling up).
    													// we set it back to FALSE when our received buffer starts to empty

    volatile int16_t    ready_to_send_timer;            // ms .. used to hold off packet transmission to wait a bit for data to mount up for transmission (improves data thru-put speed)

    volatile int32_t    not_ready_timer;                // ms .. >= 0 while we have been asked not to send anymore data to the other modem, -1 when we are allowed to send data

    bool                send_encrypted;                 // TRUE if we are to AES encrypt in every packet we transmit

    int16_t             rx_rssi_dBm;                    // the strength of the received packet
    int32_t             rx_afc_Hz;                      // the frequency offset of the received packet

} t_connection;

// *****************************************************************************

uint32_t        our_serial_number = 0;										// our serial number

t_connection    connection[PH_MAX_CONNECTIONS];								// holds each connection state

uint8_t         aes_key[AES_BLOCK_SIZE] __attribute__ ((aligned(4)));		// holds the aes encryption key - the same for ALL connections
uint8_t         dec_aes_key[AES_BLOCK_SIZE] __attribute__ ((aligned(4)));	// holds the pre-calculated decryption key
uint8_t         enc_cbc[AES_BLOCK_SIZE] __attribute__ ((aligned(4)));		// holds the tx aes cbc bytes

uint8_t         ph_tx_buffer[256] __attribute__ ((aligned(4)));				// holds the transmit packet

uint8_t         ph_rx_buffer[256] __attribute__ ((aligned(4)));				// holds the received packet

int16_t         rx_rssi_dBm;
int32_t         rx_afc_Hz;

bool			fast_ping;

// *****************************************************************************
// return TRUE if we are connected to the remote modem

bool ph_connected(const int connection_index)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return FALSE;

    t_connection *conn = &connection[connection_index];

    return (conn->link_state == LINK_CONNECTED);
}

// *****************************************************************************
// public tx buffer functions

uint16_t ph_putData_free(const int connection_index)
{	// return the free space size
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
      return 0;

    return fifoBuf_getFree(&connection[connection_index].tx_fifo_buffer);
}

uint16_t ph_putData(const int connection_index, const void *data, uint16_t len)
{	// add data to our tx buffer to be sent
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return fifoBuf_putData(&connection[connection_index].tx_fifo_buffer, data, len);
}

// *****************************************************************************
// public rx buffer functions

uint16_t ph_getData_used(const int connection_index)
{	// return the number of bytes available in the rx buffer
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
      return 0;

    return fifoBuf_getUsed(&connection[connection_index].rx_fifo_buffer);
}

uint16_t ph_getData(const int connection_index, void *data, uint16_t len)
{	// get data from our rx buffer
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return fifoBuf_getData(&connection[connection_index].rx_fifo_buffer, data, len);
}

// *****************************************************************************
// start a connection to another modem

int ph_startConnect(int connection_index, uint32_t sn)
{
    random32 = updateCRC32(random32, 0xff);

    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
      return -1;

    t_connection *conn = &connection[connection_index];

	conn->link_state = LINK_DISCONNECTED;

    LINK_LED_OFF;

	conn->serial_number = sn;

    conn->tx_sequence = 0;
    conn->tx_sequence_data_size = 0;
    conn->rx_sequence = 0;

//    fifoBuf_init(&conn->tx_fifo_buffer, conn->tx_buffer, PH_FIFO_BUFFER_SIZE);
//    fifoBuf_init(&conn->rx_fifo_buffer, conn->rx_buffer, PH_FIFO_BUFFER_SIZE);

    conn->tx_packet_timer = 0;

    conn->tx_retry_time_slots = 5;

    uint32_t ms = 1280000ul / rfm22_getDatarate();
    if (ms < 10) ms = 10;
    else
    if (ms > 32000) ms = 32000;
    conn->tx_retry_time_slot_len = ms;

    conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;

    conn->tx_retry_counter = 0;

    conn->data_speed_timer = 0;
    conn->tx_data_speed_count = 0;
    conn->tx_data_speed = 0;
    conn->rx_data_speed_count = 0;
    conn->rx_data_speed = 0;

    conn->ping_time = 8000 + (random32 % 100) * 10;
    conn->fast_ping_time = 600 + (random32 % 50) * 10;
    conn->pinging = false;

    conn->rx_not_ready_mode = false;

    conn->ready_to_send_timer = -1;

    conn->not_ready_timer = -1;

//	conn->send_encrypted = true;
//	conn->send_encrypted = false;

    conn->rx_rssi_dBm = -200;
    conn->rx_afc_Hz = 0;

    if (sn != 0 && sn == our_serial_number)
        return -2;							// same as our own

    if (sn == BROADCAST_ADDR)
    {

        return -3;
    }

    if (conn->serial_number != 0)
    	conn->link_state = LINK_CONNECTING;

    return connection_index;
}

// *****************************************************************************
// return a byte for the tx packet transmission.
//
// return value < 0 if no more bytes available, otherwise return byte to be sent

int16_t ph_TxDataByteCallback(void)
{
	return -1;
}

// *************************************************************
// we are being given a block of received bytes
//
// return TRUE to continue current packet receive, otherwise return FALSE to halt current packet reception

bool ph_RxDataCallback(void *data, uint8_t len)
{
	return true;
}

// *****************************************************************************
// transmit a packet

bool ph_sendPacket(int connection_index, bool encrypt, uint8_t packet_type, bool send_immediately)
{
  uint8_t key[AES_BLOCK_SIZE];

  t_connection *conn = NULL;

  // ***********

  t_encrypted_packet *encrypted_packet = (t_encrypted_packet*)&ph_tx_buffer;			// point to the tx buffer
  t_unencrypted_packet *unencrypted_packet = (t_unencrypted_packet*)&ph_tx_buffer;	// point to the tx buffer

  t_packet_header *header;
  uint8_t *data;
  uint16_t max_data_size;

  if (encrypt)
  {
      header = (t_packet_header *)&encrypted_packet->header;
      data = (uint8_t *)&encrypted_packet->data;
      max_data_size = sizeof(encrypted_packet->data);
  }
  else
  {
      header = (t_packet_header *)&unencrypted_packet->header;
      data = (uint8_t *)&unencrypted_packet->data;
      max_data_size = sizeof(unencrypted_packet->data);
  }

  // ***********

  if (!rfm22_txReady())
    return false;

  if ((packet_type & PACKET_TYPE_MASK) == PACKET_TYPE_NONE)
    return false;

  if (connection_index >= PH_MAX_CONNECTIONS)
    return false;

  if (connection_index >= 0)
    conn = (t_connection *)&connection[connection_index];
  else
    return false;

  // ******************
  // stuff

  uint8_t pack_type = packet_type & PACKET_TYPE_MASK;

  bool data_packet = (pack_type == PACKET_TYPE_DATA || pack_type == PACKET_TYPE_NOTREADY);

  // ******************
  // calculate how many user data bytes we are going to add to the packet

  uint16_t data_size = 0;

  if (data_packet && conn)
  {	// we're adding user data to the packet
      data_size = fifoBuf_getUsed(&connection[connection_index].tx_fifo_buffer);	// the number of data bytes waiting to be sent

      if (data_size > max_data_size)
        data_size = max_data_size;

      if (conn->tx_sequence_data_size > 0)
      {	// we are re-sending data the same data
          if (data_size > conn->tx_sequence_data_size)
            data_size = conn->tx_sequence_data_size;
      }
  }

  // ******************
  // calculate the total packet size (including null data bytes if we have to add null data byte in AES encrypted packets)

  uint32_t packet_size;

  if (encrypt)
  {
      packet_size = AES_BLOCK_SIZE + sizeof(t_packet_header) + data_size;

      // total packet size must be a multiple of 'AES_BLOCK_SIZE' bytes - aes encryption works on 16-byte blocks
      packet_size = (packet_size + (AES_BLOCK_SIZE - 1)) & ~(AES_BLOCK_SIZE - 1);
  }
  else
  {
      packet_size = 1 + sizeof(t_packet_header) + data_size;
  }

  // ******************
  // construct the packets entire header

  if (encrypt)
  {
      memmove(key, aes_key, sizeof(key));						// fetch the encryption key
      aes_encrypt_cbc_128(enc_cbc, key, NULL);				// help randomize the CBC bytes

      // ensure the 1st byte is not zero - to indicate this packet is encrypted
      while (enc_cbc[0] == 0)
      {
          random32 = updateCRC32(random32, 0xff);
          enc_cbc[0] ^= random32;
      }

      memmove(encrypted_packet->cbc, enc_cbc, AES_BLOCK_SIZE);	// copy the AES CBC bytes into the packet
  }
  else
      unencrypted_packet->null_byte = 0;						// packet is not encrypted

  header->source_id = our_serial_number;						// our serial number
//      header->destination_id = BROADCAST_ADDR;					// broadcast packet
  header->destination_id = conn->serial_number;				// the other modems serial number
  header->type = packet_type;									// packet type
  header->tx_seq = conn->tx_sequence;							// our TX sequence number
  header->rx_seq = conn->rx_sequence;							// our RX sequence number
  header->data_size = data_size;								// the number of user data bytes in the packet
  header->crc = 0;											// the CRC of the header and user data bytes

  // ******************
  // add the user data to the packet

  if (data_packet)
  {	// we're adding user data to the packet
      fifoBuf_getDataPeek(&connection[connection_index].tx_fifo_buffer, data, data_size);

      if (encrypt)
      {	// zero unused bytes
          if (data_size < max_data_size)
            memset(data + data_size, 0, max_data_size - data_size);
      }

      conn->tx_sequence_data_size = data_size;	// remember how much data we are sending in this packet
  }

  // ******************
  // complete the packet header by adding the CRC

  if (encrypt)
    header->crc = updateCRC32Data(0xffffffff, header, packet_size - AES_BLOCK_SIZE);
  else
    header->crc = updateCRC32Data(0xffffffff, header, packet_size - 1);

  // ******************
  // encrypt the packet

  if (encrypt)
  {	// encrypt the packet .. 'AES_BLOCK_SIZE' bytes at a time
      uint8_t *p = (uint8_t *)encrypted_packet;

      // encrypt the cbc
      memmove(key, aes_key, sizeof(key));				// fetch the encryption key
      aes_encrypt_cbc_128(p, key, NULL);				// encrypt block of data (the CBC bytes)
      p += AES_BLOCK_SIZE;

      // encrypt the rest of the packet
      for (uint16_t i = AES_BLOCK_SIZE; i < packet_size; i += AES_BLOCK_SIZE)
      {
          memmove(key, aes_key, sizeof(key));			// fetch the encryption key
          aes_encrypt_cbc_128(p, key, enc_cbc);		// encrypt block of data
          p += AES_BLOCK_SIZE;
      }
  }

  // ******************
  // send the packet

  int32_t res = rfm22_sendData(&ph_tx_buffer, packet_size, send_immediately);

  // ******************

  if (data_size > 0 && conn->tx_retry_counter == 0)
    conn->tx_data_speed_count += data_size * 8;	// + the number of data bits we just sent .. used for calculating the transmit data rate

  // ******************
  // debug stuff

#if defined(PACKET_DEBUG)

  DEBUG_PRINTF("T-PACK ");
  switch (pack_type)
  {
    case PACKET_TYPE_NONE:              DEBUG_PRINTF("none"); break;
    case PACKET_TYPE_CONNECT:           DEBUG_PRINTF("connect"); break;
    case PACKET_TYPE_CONNECT_ACK:       DEBUG_PRINTF("connect_ack"); break;
    case PACKET_TYPE_DISCONNECT:        DEBUG_PRINTF("disconnect"); break;
    case PACKET_TYPE_DATA:              DEBUG_PRINTF("data"); break;
    case PACKET_TYPE_DATA_ACK:          DEBUG_PRINTF("data_ack"); break;
    case PACKET_TYPE_READY:             DEBUG_PRINTF("ready"); break;
    case PACKET_TYPE_READY_ACK:         DEBUG_PRINTF("ready_ack"); break;
    case PACKET_TYPE_NOTREADY:          DEBUG_PRINTF("notready"); break;
    case PACKET_TYPE_NOTREADY_ACK:      DEBUG_PRINTF("notready_ack"); break;
    case PACKET_TYPE_DATARATE:          DEBUG_PRINTF("datarate"); break;
    case PACKET_TYPE_DATARATE_ACK:      DEBUG_PRINTF("datarate_ack"); break;
    case PACKET_TYPE_PING:              DEBUG_PRINTF("ping"); break;
    case PACKET_TYPE_PONG:              DEBUG_PRINTF("pong"); break;
    case PACKET_TYPE_ADJUST_TX_PWR:     DEBUG_PRINTF("PACKET_TYPE_ADJUST_TX_PWR"); break;
    case PACKET_TYPE_ADJUST_TX_PWR_ACK: DEBUG_PRINTF("PACKET_TYPE_ADJUST_TX_PWR_ACK"); break;
    default:                            DEBUG_PRINTF("UNKNOWN [%d]", pack_type); break;
  }
  DEBUG_PRINTF(" tseq:%d rseq:%d", conn->tx_sequence, conn->rx_sequence);
  DEBUG_PRINTF(" drate:%dbps", conn->tx_data_speed);
  if (data_size > 0) DEBUG_PRINTF(" data_size:%d", data_size);
  if (conn->tx_retry_counter > 0) DEBUG_PRINTF(" retry:%d", conn->tx_retry_counter);
  DEBUG_PRINTF("\r\n");
#endif

  // ******************

  switch (pack_type)
  {
    case PACKET_TYPE_CONNECT:
    case PACKET_TYPE_DISCONNECT:
    case PACKET_TYPE_DATA:
    case PACKET_TYPE_READY:
    case PACKET_TYPE_NOTREADY:
    case PACKET_TYPE_DATARATE:
    case PACKET_TYPE_PING:
    case PACKET_TYPE_ADJUST_TX_PWR:
      if (conn->tx_retry_counter < 0xffff)
        conn->tx_retry_counter++;
      break;

    case PACKET_TYPE_CONNECT_ACK:
    case PACKET_TYPE_DATA_ACK:
    case PACKET_TYPE_READY_ACK:
    case PACKET_TYPE_NOTREADY_ACK:
    case PACKET_TYPE_DATARATE_ACK:
    case PACKET_TYPE_PONG:
    case PACKET_TYPE_ADJUST_TX_PWR_ACK:
      break;

    case PACKET_TYPE_NONE:
      break;
  }

  return (res >= packet_size);
}

// *****************************************************************************

void ph_processPacket2(bool was_encrypted, t_packet_header *header, uint8_t *data)
{	// process the received decrypted error-free packet

  USB_LED_TOGGLE;			// TEST ONLY

  // ***********

  // fetch the data compressed bit
  bool compressed_data = (header->type & PACKET_TYPE_DATA_COMP_BIT) != 0;

  // fetch the packet type
  uint8_t packet_type = header->type & PACKET_TYPE_MASK;

  // fetch the number of data bytes in the packet
  uint16_t data_size = header->data_size;

  // update the ramdon number
  random32 = updateCRC32(random32, 0xff);

  // *********************
  // debug stuff
/*
#if defined(PACKET_DEBUG)
	if (data_size > 0)
	{
		DEBUG_PRINTF("rx packet:");
		for (uint16_t i = 0; i < data_size; i++)
			DEBUG_PRINTF(" %u", data[i]);
		DEBUG_PRINTF("\r\n");
	}
#endif
*/
  // ***********
  // debug stuff

#if defined(PACKET_DEBUG)
  DEBUG_PRINTF("R-PACK ");
  switch (packet_type)
  {
    case PACKET_TYPE_NONE:              DEBUG_PRINTF("none"); break;
    case PACKET_TYPE_CONNECT:           DEBUG_PRINTF("connect"); break;
    case PACKET_TYPE_CONNECT_ACK:       DEBUG_PRINTF("connect_ack"); break;
    case PACKET_TYPE_DISCONNECT:        DEBUG_PRINTF("disconnect"); break;
    case PACKET_TYPE_DATA:              DEBUG_PRINTF("data"); break;
    case PACKET_TYPE_DATA_ACK:          DEBUG_PRINTF("data_ack"); break;
    case PACKET_TYPE_READY:             DEBUG_PRINTF("ready"); break;
    case PACKET_TYPE_READY_ACK:         DEBUG_PRINTF("ready_ack"); break;
    case PACKET_TYPE_NOTREADY:          DEBUG_PRINTF("notready"); break;
    case PACKET_TYPE_NOTREADY_ACK:      DEBUG_PRINTF("notready_ack"); break;
    case PACKET_TYPE_DATARATE:          DEBUG_PRINTF("datarate"); break;
    case PACKET_TYPE_DATARATE_ACK:      DEBUG_PRINTF("datarate_ack"); break;
    case PACKET_TYPE_PING:              DEBUG_PRINTF("ping"); break;
    case PACKET_TYPE_PONG:              DEBUG_PRINTF("pong"); break;
    case PACKET_TYPE_ADJUST_TX_PWR:     DEBUG_PRINTF("PACKET_TYPE_ADJUST_TX_PWR"); break;
    case PACKET_TYPE_ADJUST_TX_PWR_ACK: DEBUG_PRINTF("PACKET_TYPE_ADJUST_TX_PWR_ACK"); break;
    default:                            DEBUG_PRINTF("UNKNOWN [%d]", packet_type); break;
  }
  DEBUG_PRINTF(" tseq-%d rseq-%d", header->tx_seq, header->rx_seq);
//      DEBUG_PRINTF(" drate:%dbps", conn->rx_data_speed);
  if (data_size > 0) DEBUG_PRINTF(" data_size:%d", data_size);
  DEBUG_PRINTF(" %ddBm", rx_rssi_dBm);
  DEBUG_PRINTF(" %dHz", rx_afc_Hz);
  DEBUG_PRINTF("\r\n");
#endif

  // *********************

  if (header->source_id == our_serial_number)
    return;	// it's our own packet .. ignore it

  if (header->destination_id == BROADCAST_ADDR)
  {	// it's a broadcast packet



		// todo:




      return;
  }

  if (header->destination_id != our_serial_number)
    return;		// the packet is not meant for us

  // *********************
  // find out which remote connection this packet is from

  int connection_index = 0;
  while (connection_index < PH_MAX_CONNECTIONS)
  {
      uint32_t sn = connection[connection_index].serial_number;
      if (sn != 0)
      {	// connection used
          if (header->source_id == sn)
            break;	// found it
      }
      connection_index++;
  }

  if (connection_index >= PH_MAX_CONNECTIONS)
  {	// the packet is from an unknown source ID (unknown modem)

      if (packet_type != PACKET_TYPE_NONE)
      {	// send a disconnect packet back to them
//              ph_sendPacket(-1, was_encrypted, PACKET_TYPE_DISCONNECT, true);
      }

      return;
  }

  t_connection *conn = &connection[connection_index];

  // ***********

  conn->rx_rssi_dBm = rx_rssi_dBm;				// remember the packets signal strength
  conn->rx_afc_Hz = rx_afc_Hz;					// remember the packets frequency offset

  // ***********
  // decompress the data

  if (compressed_data && data_size > 0)
  {


      // todo:


  }

  // ***********

  if (packet_type == PACKET_TYPE_NONE)
    return;

  if (packet_type == PACKET_TYPE_DISCONNECT)
  {
      conn->link_state = LINK_DISCONNECTED;
      LINK_LED_OFF;
      return;
  }

  if (packet_type == PACKET_TYPE_CONNECT)
  {
      LINK_LED_ON;

//      fifoBuf_init(&conn->tx_fifo_buffer, conn->tx_buffer, PH_FIFO_BUFFER_SIZE);
//      fifoBuf_init(&conn->rx_fifo_buffer, conn->rx_buffer, PH_FIFO_BUFFER_SIZE);

      conn->tx_packet_timer = 0;

      conn->tx_retry_counter = 0;
      conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;

      conn->rx_sequence = header->tx_seq;
      conn->tx_sequence = 0;
      conn->tx_sequence_data_size = 0;

      conn->data_speed_timer = 0;
      conn->tx_data_speed_count = 0;
      conn->tx_data_speed = 0;
      conn->rx_data_speed_count = 0;
      conn->rx_data_speed = 0;

      conn->ping_time = 8000 + (random32 % 100) * 10;
      conn->fast_ping_time = 600 + (random32 % 50) * 10;
      conn->pinging = false;

      conn->rx_not_ready_mode = false;

      conn->ready_to_send_timer = -1;

      conn->not_ready_timer = -1;

      conn->link_state = LINK_CONNECTED;

      // send an ack back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_CONNECT_ACK, true))
      {
          conn->tx_packet_timer = 0;
      }

      return;
  }

  if (packet_type == PACKET_TYPE_CONNECT_ACK)
  {
      LINK_LED_ON;

      if (conn->link_state != LINK_CONNECTING)
      {	// reset the link
          ph_set_remote_serial_number(connection_index, conn->serial_number);
          return;
      }

      conn->tx_packet_timer = 0;

      conn->tx_retry_counter = 0;
      conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;

      conn->rx_sequence = header->tx_seq;
      conn->tx_sequence = 0;
      conn->tx_sequence_data_size = 0;

      conn->data_speed_timer = 0;
      conn->tx_data_speed_count = 0;
      conn->tx_data_speed = 0;
      conn->rx_data_speed_count = 0;
      conn->rx_data_speed = 0;

      conn->ping_time = 8000 + (random32 % 100) * 10;
      conn->fast_ping_time = 600 + (random32 % 50) * 10;
      conn->pinging = false;

      conn->rx_not_ready_mode = false;

      conn->ready_to_send_timer = -1;

      conn->not_ready_timer = -1;

      conn->link_state = LINK_CONNECTED;

      return;
  }




  if (conn->link_state == LINK_CONNECTING)
  {	// we are trying to connect to them .. reply with a connect request packet
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_CONNECT, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
      }
      return;
  }

  if (conn->link_state != LINK_CONNECTED)
  {	// they have sent us a packet when we are not in a connected state - start a connection
      ph_startConnect(connection_index, conn->serial_number);
      return;
  }




  // check to make sure it's a wanted packet type
  switch (packet_type)
  {
    case PACKET_TYPE_DATA:
    case PACKET_TYPE_DATA_ACK:
    case PACKET_TYPE_READY:
    case PACKET_TYPE_READY_ACK:
    case PACKET_TYPE_NOTREADY:
    case PACKET_TYPE_NOTREADY_ACK:
    case PACKET_TYPE_DATARATE:
    case PACKET_TYPE_DATARATE_ACK:
    case PACKET_TYPE_PING:
    case PACKET_TYPE_PONG:
    case PACKET_TYPE_ADJUST_TX_PWR:
    case PACKET_TYPE_ADJUST_TX_PWR_ACK:
      break;
    default:
      return;
  }





  if ((conn->tx_sequence_data_size > 0) && (header->rx_seq == (uint8_t)(conn->tx_sequence + 1)))
  {	// they received our last data packet

      // remove the data we have sent and they have acked
      fifoBuf_removeData(&conn->tx_fifo_buffer, conn->tx_sequence_data_size);

      conn->tx_sequence++;
      conn->tx_retry_counter = 0;
      conn->tx_sequence_data_size = 0;
      conn->not_ready_timer = -1;	// stop timer
  }

  uint16_t size = fifoBuf_getUsed(&conn->tx_fifo_buffer);	// the size of data waiting to be sent




  if (packet_type == PACKET_TYPE_DATA || packet_type == PACKET_TYPE_DATA_ACK)
  {
      if (packet_type == PACKET_TYPE_DATA && header->tx_seq == conn->rx_sequence)
      {	// the packet number is what we expected

          if (data_size > 0)
          {	// save the data

              conn->rx_data_speed_count += data_size * 8;	// + the number of data bits we just received

              uint16_t num = fifoBuf_getFree(&conn->rx_fifo_buffer);
              if (num < data_size)
              {	// error .. we don't have enough space left in our fifo buffer to save the data .. discard it and tell them to hold off a sec
//                      conn->rx_not_ready_mode = true;
              }
              else
              {	// save the received data into our fifo buffer
                  fifoBuf_putData(&conn->rx_fifo_buffer, data, data_size);
                  conn->rx_sequence++;
                  conn->rx_not_ready_mode = false;
              }
          }
      }

      if (size >= 200 || (conn->ready_to_send_timer >= 10 && size > 0) || (conn->tx_sequence_data_size > 0 && size > 0))
      {	// send data
          uint8_t pack_type = PACKET_TYPE_DATA;
          if (conn->rx_not_ready_mode)
            pack_type = PACKET_TYPE_NOTREADY;

          if (ph_sendPacket(connection_index, conn->send_encrypted, pack_type, true))
          {
              conn->tx_packet_timer = 0;
              conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
              return;
          }
      }

      if (packet_type == PACKET_TYPE_DATA)
      {	// send an ack back
          uint8_t pack_type = PACKET_TYPE_DATA_ACK;
          if (conn->rx_not_ready_mode)
            pack_type = PACKET_TYPE_NOTREADY_ACK;

          if (ph_sendPacket(connection_index, conn->send_encrypted, pack_type, true))
          {
              conn->tx_packet_timer = 0;
              conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
              return;
          }
      }

      return;
  }

  if (packet_type == PACKET_TYPE_READY)
  {
      conn->not_ready_timer = -1;	// stop timer

      // send an ack back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_READY_ACK, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
          return;
      }

      return;
  }

  if (packet_type == PACKET_TYPE_READY_ACK)
  {
      conn->rx_not_ready_mode = false;
      return;
  }

  if (packet_type == PACKET_TYPE_NOTREADY)
  {
//      conn->not_ready_timer = 0;	// start timer

      if (header->tx_seq == conn->rx_sequence)
      {	// the packet number is what we expected

          if (data_size > 0)
          {	// save the data

              conn->rx_data_speed_count += data_size * 8;	// + the number of data bits we just received

              uint16_t num = fifoBuf_getFree(&conn->rx_fifo_buffer);
              if (num < data_size)
              {	// error .. we don't have enough space left in our fifo buffer to save the data .. discard it and tell them to hold off a sec
//              conn->rx_not_ready_mode = true;
              }
              else
              {	// save the received data into our fifo buffer
                  fifoBuf_putData(&conn->rx_fifo_buffer, data, data_size);
                  conn->rx_sequence++;
                  conn->rx_not_ready_mode = false;
              }
          }
      }

      // send an ack back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_NOTREADY_ACK, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
          return;
      }

      return;
  }

  if (packet_type == PACKET_TYPE_NOTREADY_ACK)
  {
      return;
  }

  if (packet_type == PACKET_TYPE_PING)
  {	// send a pong back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_PONG, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
      }
      return;
  }

  if (packet_type == PACKET_TYPE_PONG)
  {
      if (conn->pinging)
      {
          conn->pinging = false;
          conn->tx_retry_counter = 0;
      }
      return;
  }

  if (packet_type == PACKET_TYPE_DATARATE)
  {
      // send an ack back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_DATARATE_ACK, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
      }
      return;
  }

  if (packet_type == PACKET_TYPE_DATARATE_ACK)
  {
      return;
  }

  if (packet_type == PACKET_TYPE_ADJUST_TX_PWR)
  {
      // send an ack back
      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_ADJUST_TX_PWR_ACK, true))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
      }
      return;
  }

  if (packet_type == PACKET_TYPE_ADJUST_TX_PWR_ACK)
  {
      return;
  }

  // *********************
}

void ph_processRxPacket(void)
{
  uint32_t crc1, crc2;
  uint8_t key[AES_BLOCK_SIZE];
  register uint8_t *p;

  // ***********
  // fetch the received packet

  uint16_t packet_size = rfm22_receivedLength();
  if (packet_size == 0)
    return;					// nothing received

  if (packet_size > sizeof(ph_rx_buffer))
  {	// packet too big .. discard it
      rfm22_receivedDone();
      return;
  }

  rx_rssi_dBm = rfm22_receivedRSSI();	// fetch the packets signal stength
  rx_afc_Hz = rfm22_receivedAFCHz();	// fetch the packets frequency offset

  // copy the received packet into our own buffer
  memmove(ph_rx_buffer, rfm22_receivedPointer(), packet_size);

  rfm22_receivedDone();				// the received packet has been saved

  // *********************
  // if the 1st byte in the packet is not zero, then the packet is encrypted

  bool encrypted = (ph_rx_buffer[0] != 0);

  // ***********

  t_encrypted_packet *encrypted_packet = (t_encrypted_packet *)&ph_rx_buffer;			// point to the rx buffer
  t_unencrypted_packet *unencrypted_packet = (t_unencrypted_packet *)&ph_rx_buffer;	// point to the rx buffer

  t_packet_header *header;
  uint8_t *data;
  uint16_t min_packet_size;
  uint16_t max_data_size;

  if (encrypted)
  {
      header = (t_packet_header *)&encrypted_packet->header;
      data = (uint8_t *)&encrypted_packet->data;
      min_packet_size = AES_BLOCK_SIZE + sizeof(t_packet_header);
      max_data_size = sizeof(encrypted_packet->data);
  }
  else
  {
      header = (t_packet_header *)&unencrypted_packet->header;
      data = (uint8_t *)&unencrypted_packet->data;
      min_packet_size = 1 + sizeof(t_packet_header);
      max_data_size = sizeof(unencrypted_packet->data);
  }

  if (packet_size < min_packet_size)
  {	// packet too small .. discard it
      return;
  }

  random32 = updateCRC32(random32 ^ header->crc, 0xff);	// help randomize the random number

  // *********************
  // help to randomize the tx aes cbc bytes by using the received packet

  p = (uint8_t *)&ph_rx_buffer;
  for (uint16_t i = 0; i < packet_size; i += AES_BLOCK_SIZE)
  {
      for (int j = AES_BLOCK_SIZE - 1; j >= 0; j--)
        enc_cbc[j] ^= *p++;
  }

  // *********************
  // check the packet size

  if (encrypted)
  {
      if ((packet_size & (AES_BLOCK_SIZE - 1)) != 0)
        return;	// packet must be a multiple of 'AES_BLOCK_SIZE' bytes in length - for the aes decryption
  }

  // *********************
  // decrypt the packet

  if (encrypted)
  {
      p = (uint8_t *)encrypted_packet;						// point to the received packet

      // decrypt the cbc
      memmove(key, (void *)dec_aes_key, sizeof(key));			// fetch the decryption key
      aes_decrypt_cbc_128(p, key, NULL);						// decrypt the cbc bytes
      p += AES_BLOCK_SIZE;

      // decrypt the rest of the packet
      for (uint16_t i = AES_BLOCK_SIZE; i < packet_size; i += AES_BLOCK_SIZE)
      {
          memmove(key, (void *)dec_aes_key, sizeof(key));		// fetch the decryption key
          aes_decrypt_cbc_128(p, key, (void *)encrypted_packet->cbc);
          p += AES_BLOCK_SIZE;
      }
  }

  // *********************

#if defined(PACKET_DEBUG)
  DEBUG_PRINTF("rx packet: ");
  DEBUG_PRINTF("%s", encrypted ? "encrypted " : "unencrypted");
  if (encrypted)
  {
      for (int i = 0; i < AES_BLOCK_SIZE; i++)
        DEBUG_PRINTF("%02X", encrypted_packet->cbc[i]);
  }
  DEBUG_PRINTF(" %08X %08X %u %u %u %u %08X\r\n",
      header->source_id,
      header->destination_id,
      header->type,
      header->tx_seq,
      header->rx_seq,
      header->data_size,
      header->crc);

  if (header->data_size > 0)
  {
      DEBUG_PRINTF("rx packet [%u]: ", header->data_size);
      for (int i = 0; i < header->data_size; i++)
        DEBUG_PRINTF("%02X", data[i]);
      DEBUG_PRINTF("\r\n");
  }
#endif

  // *********************

  uint32_t data_size = header->data_size;

  if (packet_size < (min_packet_size + data_size))
    return;				// packet too small

#if defined(PACKET_DEBUG)
//      DEBUG_PRINTF("rx packet size: %d\r\n", packet_size);
#endif

  // *********************
  // check the packet is error free

  crc1 = header->crc;
  header->crc = 0;
  if (encrypted)
    crc2 = updateCRC32Data(0xffffffff, header, packet_size - AES_BLOCK_SIZE);
  else
    crc2 = updateCRC32Data(0xffffffff, header, packet_size - 1);
  if (crc1 != crc2)
  {	// corrupt packet
      #if defined(PACKET_DEBUG)
        if (encrypted)
          DEBUG_PRINTF("ENC-R-PACK corrupt %08X %08X\r\n", crc1, crc2);
        else
          DEBUG_PRINTF("R-PACK corrupt %08X %08X\r\n", crc1, crc2);
      #endif
      return;
  }

  // *********************
  // process the data

  ph_processPacket2(encrypted, header, data);

  // *********************
}

// *****************************************************************************
// do all the link/packet handling stuff - request connection/disconnection, send data, acks etc

void ph_processLinks(int connection_index)
{
  if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
    return;

  random32 = updateCRC32(random32, 0xff);

  t_connection *conn = &connection[connection_index];

  bool canTx = (!rfm22_transmitting() && rfm22_channelIsClear());// TRUE is we can transmit

  bool timeToRetry = (rfm22_txReady() && conn->tx_packet_timer >= conn->tx_retry_time);

  bool tomanyRetries = (conn->tx_retry_counter >= RETRY_RECONNECT_COUNT);

  if (conn->tx_retry_counter > 3)
	  conn->rx_rssi_dBm = -200;

  switch (conn->link_state)
  {
    case LINK_DISCONNECTED:
      if (!canTx)
      {
          conn->tx_packet_timer = 0;
          break;
      }

      if (!rfm22_txReady() || conn->tx_packet_timer < 60000)
        break;

      if (our_serial_number != 0 && conn->serial_number != 0)
      {	// try to reconnect with the remote modem
          ph_startConnect(connection_index, conn->serial_number);
          break;
      }

      break;

    case LINK_CONNECTING:
      if (!canTx)
      {
          conn->tx_packet_timer = 0;
          break;
      }

      if (!timeToRetry)
        break;

      if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_CONNECT, false))
      {
          conn->tx_packet_timer = 0;
          conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
          break;
      }

      break;

    case LINK_CONNECTED:
      if (!canTx)
      {
          conn->tx_packet_timer = 0;
          break;
      }

      if (!timeToRetry)
        break;

      if (tomanyRetries)
      {	// reset the link if we have sent tomany retries
          ph_startConnect(connection_index, conn->serial_number);
          break;
      }

      if (conn->pinging)
      {	// we are trying to ping them
          if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_PING, false))
          {
              conn->tx_packet_timer = 0;
              conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
          }
          break;
      }

      uint16_t ping_time = conn->ping_time;
      if (fast_ping) ping_time = conn->fast_ping_time;
      if (conn->tx_packet_timer >= ping_time)
      {	// start pinging
          if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_PING, false))
          {
              conn->ping_time = 8000 + (random32 % 100) * 10;
              conn->fast_ping_time = 600 + (random32 % 50) * 10;
              conn->tx_packet_timer = 0;
              conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
              conn->pinging = true;
          }
          break;
      }

      // ***********
      // exit rx not ready mode if we have space in our rx buffer for more data
/*
			if (conn->rx_not_ready_mode)
			{
				uint16_t size = fifoBuf_getFree(&conn->rx_fifo_buffer);
				if (size >= conn->rx_fifo_buffer.buf_size / 6)
				{	// leave 'rx not ready' mode
					if (ph_sendPacket(connection_index, conn->send_encrypted, PACKET_TYPE_READY, false))
					{
						conn->tx_packet_timer = 0;
						conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
						break;
					}
				}
			}
*/
			// ***********
			// send data packets

//			if (conn->not_ready_timer < 0)
      {
        uint16_t size = fifoBuf_getUsed(&conn->tx_fifo_buffer);

        if (size == 0)
          conn->ready_to_send_timer = -1;	// no data to send
        else
        if (conn->ready_to_send_timer < 0)
          conn->ready_to_send_timer = 0;	// start timer

        if (size >= 200 || (conn->ready_to_send_timer >= saved_settings.rts_time && size > 0) || (conn->tx_sequence_data_size > 0 && size > 0))
        {       // send data

            uint8_t pack_type = PACKET_TYPE_DATA;
            if (conn->rx_not_ready_mode)
              pack_type = PACKET_TYPE_NOTREADY;

            if (ph_sendPacket(connection_index, conn->send_encrypted, pack_type, false))
            {
                conn->tx_packet_timer = 0;
                conn->tx_retry_time = conn->tx_retry_time_slot_len + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len;
            }
            break;
        }
      }

      // ***********

      break;

    default:	// we should never end up here - maybe we should do a reboot?
      conn->link_state = LINK_DISCONNECTED;
/*
			// disable all interrupts
			PIOS_IRQ_Disable();

			// turn off all leds
			USB_LED_OFF;
			LINK_LED_OFF;
			RX_LED_OFF;
			TX_LED_OFF;

			PIOS_SYS_Reset();

			while (1);
*/
      break;
  }
}

// *****************************************************************************

void ph_setFastPing(bool fast)
{
	fast_ping = fast;
}

// *****************************************************************************

uint8_t ph_getCurrentLinkState(const int connection_index)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return connection[connection_index].link_state;
}

// *****************************************************************************

uint16_t ph_getRetries(const int connection_index)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return connection[connection_index].tx_retry_counter;
}

// *****************************************************************************

int16_t ph_getLastRSSI(const int connection_index)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return connection[connection_index].rx_rssi_dBm;
}

int32_t ph_getLastAFC(const int connection_index)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return 0;

    return connection[connection_index].rx_afc_Hz;
}

// *****************************************************************************
// set/get the carrier frequency

void ph_setNominalCarrierFrequency(uint32_t frequency_hz)
{
	rfm22_setNominalCarrierFrequency(frequency_hz);
}

uint32_t ph_getNominalCarrierFrequency(void)
{
	return rfm22_getNominalCarrierFrequency();
}

// *****************************************************************************
// set/get the RF datarate

void ph_setDatarate(uint32_t datarate_bps)
{
  rfm22_setDatarate(datarate_bps, TRUE);

  uint32_t ms = 1280000ul / rfm22_getDatarate();
  if (ms < 10) ms = 10;
  else
  if (ms > 32000) ms = 32000;

  for (int i = 0; i < PH_MAX_CONNECTIONS; i++)
    connection[i].tx_retry_time_slot_len = ms;
}

uint32_t ph_getDatarate(void)
{
  return rfm22_getDatarate();
}

// *****************************************************************************

void ph_setTxPower(uint8_t tx_power)
{
  rfm22_setTxPower(tx_power);
}

uint8_t ph_getTxPower(void)
{
  return rfm22_getTxPower();
}

// *****************************************************************************
// set the AES encryption key

void ph_set_AES128_key(const void *key)
{
	if (!key)
		return;

	memmove(aes_key, key, sizeof(aes_key));

	// create the AES decryption key
	aes_decrypt_key_128_create(aes_key, (void *)&dec_aes_key);
}

// *****************************************************************************

int ph_set_remote_serial_number(int connection_index, uint32_t sn)
{
  random32 = updateCRC32(random32, 0xff);

  if (ph_startConnect(connection_index, sn) >= 0)
  {
      t_connection *conn = &connection[connection_index];

      // wipe any user data present in the buffers
      fifoBuf_init(&conn->tx_fifo_buffer, conn->tx_buffer, PH_FIFO_BUFFER_SIZE);
      fifoBuf_init(&conn->rx_fifo_buffer, conn->rx_buffer, PH_FIFO_BUFFER_SIZE);

      return connection_index;
  }

  return -4;
}

void ph_set_remote_encryption(int connection_index, bool enabled, const void *key)
{
    if (connection_index < 0 || connection_index >= PH_MAX_CONNECTIONS)
        return;

    ph_set_AES128_key(key);

    connection[connection_index].send_encrypted = enabled;
}

// *****************************************************************************
// can be called from an interrupt if you wish.
// call this once every ms

void ph_1ms_tick(void)
{
	if (booting) return;

	if (saved_settings.mode == MODE_NORMAL)
	{
		// help randomize the encryptor cbc bytes
		register uint32_t *cbc = (uint32_t *)&enc_cbc;
		for (int i = 0; i < sizeof(enc_cbc) / 4; i++)
		{
			random32 = updateCRC32(random32, 0xff);
			*cbc++ ^= random32;
		}

		for (int i = 0; i < PH_MAX_CONNECTIONS; i++)
		{
			t_connection *conn = &connection[i];

			if (conn->tx_packet_timer < 0xffff)
				conn->tx_packet_timer++;

			if (conn->link_state == LINK_CONNECTED)
			{	// we are connected

				if (conn->ready_to_send_timer >= 0 && conn->ready_to_send_timer < 0x7fff)
					conn->ready_to_send_timer++;

				if (conn->not_ready_timer >= 0 && conn->not_ready_timer < 0x7fffffff)
					conn->not_ready_timer++;

				if (conn->data_speed_timer < 0xffff)
				{
					if (++conn->data_speed_timer >= 1000)
					{	// 1 second gone by
						conn->data_speed_timer = 0;
						conn->tx_data_speed = (conn->tx_data_speed + conn->tx_data_speed_count) >> 1;
						conn->tx_data_speed_count = 0;
						conn->rx_data_speed = (conn->rx_data_speed + conn->rx_data_speed_count) >> 1;
						conn->rx_data_speed_count = 0;
					}
				}
			}
			else
			{	// we are not connected
				if (conn->data_speed_timer) conn->data_speed_timer = 0;
				if (conn->tx_data_speed_count) conn->tx_data_speed_count = 0;
				if (conn->tx_data_speed) conn->tx_data_speed = 0;
				if (conn->rx_data_speed_count) conn->rx_data_speed_count = 0;
				if (conn->rx_data_speed) conn->rx_data_speed = 0;
			}
		}
	}
}

// *****************************************************************************
// call this as often as possible - not from an interrupt

void ph_process(void)
{
	if (booting) return;

	if (saved_settings.mode == MODE_NORMAL)
	{
		ph_processRxPacket();

		for (int i = 0; i < PH_MAX_CONNECTIONS; i++)
			ph_processLinks(i);
	}
	else
	{

	}
}

// *****************************************************************************

void ph_disconnectAll(void)
{
	for (int i = 0; i < PH_MAX_CONNECTIONS; i++)
	{
		random32 = updateCRC32(random32, 0xff);

		t_connection *conn = &connection[i];

		conn->serial_number = 0;

		conn->tx_sequence = 0;
		conn->tx_sequence_data_size = 0;

		conn->rx_sequence = 0;

		fifoBuf_init(&conn->tx_fifo_buffer, conn->tx_buffer, PH_FIFO_BUFFER_SIZE);
		fifoBuf_init(&conn->rx_fifo_buffer, conn->rx_buffer, PH_FIFO_BUFFER_SIZE);

		conn->link_state = LINK_DISCONNECTED;

		conn->tx_packet_timer = 0;

		conn->tx_retry_time_slots = 5;
		conn->tx_retry_time_slot_len = 40;
		conn->tx_retry_time = conn->tx_retry_time_slot_len * 4 + (random32 % conn->tx_retry_time_slots) * conn->tx_retry_time_slot_len * 4;
		conn->tx_retry_counter = 0;

		conn->data_speed_timer = 0;
		conn->tx_data_speed_count = 0;
		conn->tx_data_speed = 0;
		conn->rx_data_speed_count = 0;
		conn->rx_data_speed = 0;

		conn->ping_time = 8000 + (random32 % 100) * 10;
		conn->fast_ping_time = 600 + (random32 % 50) * 10;
		conn->pinging = false;

		conn->rx_not_ready_mode = false;

		conn->ready_to_send_timer = -1;

		conn->not_ready_timer = -1;

		conn->send_encrypted = false;

		conn->rx_rssi_dBm = -200;
		conn->rx_afc_Hz = 0;
	}
}

// *****************************************************************************

void ph_deinit(void)
{
	ph_disconnectAll();
}

void ph_init(uint32_t our_sn)
{
	our_serial_number = our_sn;	// remember our own serial number

	fast_ping = false;

	ph_disconnectAll();

	// set the AES encryption key using the default AES key
	ph_set_AES128_key(default_aes_key);

	// try too randomise the tx AES CBC bytes
	for (uint32_t j = 0, k = 0; j < 123 + (random32 & 1023); j++)
	{
		random32 = updateCRC32(random32, 0xff);
		enc_cbc[k] ^= random32 >> 3;
		if (++k >= sizeof(enc_cbc)) k = 0;
	}

	// ******

	rfm22_init_normal(saved_settings.min_frequency_Hz, saved_settings.max_frequency_Hz, rfm22_freqHopSize());

	rfm22_TxDataByte_SetCallback(ph_TxDataByteCallback);
	rfm22_RxData_SetCallback(ph_RxDataCallback);

	rfm22_setFreqCalibration(saved_settings.rf_xtal_cap);
	ph_setNominalCarrierFrequency(saved_settings.frequency_Hz);
	ph_setDatarate(saved_settings.max_rf_bandwidth);
	ph_setTxPower(saved_settings.max_tx_power);

	ph_set_remote_encryption(0, saved_settings.aes_enable, (const void *)saved_settings.aes_key);
	ph_set_remote_serial_number(0, saved_settings.destination_id);

	// ******
}

// *****************************************************************************